diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 2407bb60b3..0b0e3cbfcd 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -72,6 +72,7 @@ set( KICOMMON_SRCS jobs/job_export_pcb_gerber.cpp jobs/job_export_pcb_gerbers.cpp jobs/job_export_pcb_ipc2581.cpp + jobs/job_export_pcb_ipcd356.cpp jobs/job_export_pcb_odb.cpp jobs/job_export_pcb_pdf.cpp jobs/job_export_pcb_plot.cpp diff --git a/common/jobs/job_export_pcb_ipcd356.cpp b/common/jobs/job_export_pcb_ipcd356.cpp new file mode 100644 index 0000000000..50184b5302 --- /dev/null +++ b/common/jobs/job_export_pcb_ipcd356.cpp @@ -0,0 +1,53 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2025 Connor Goss + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include +#include +#include +#include + +JOB_EXPORT_PCB_IPCD356::JOB_EXPORT_PCB_IPCD356() : JOB( "ipcd356", false ), m_filename() +{ +} + + +wxString JOB_EXPORT_PCB_IPCD356::GetDefaultDescription() const +{ + return _( "Export IPC-D-356" ); +} + + +wxString JOB_EXPORT_PCB_IPCD356::GetSettingsDialogTitle() const +{ + return _( "Export IPC-D-356 Job Settings" ); +} + + +void JOB_EXPORT_PCB_IPCD356::SetDefaultOutputPath( const wxString& aReferenceName ) +{ + wxFileName fn = aReferenceName; + + fn.SetExt( FILEEXT::IpcD356FileExtension ); + + SetConfiguredOutputPath( fn.GetFullName() ); +} + +REGISTER_JOB( pcb_export_ipcd356, _HKI( "PCB: Export IPC-D-356" ), KIWAY::FACE_PCB, + JOB_EXPORT_PCB_IPCD356 ); diff --git a/common/jobs/job_export_pcb_ipcd356.h b/common/jobs/job_export_pcb_ipcd356.h new file mode 100644 index 0000000000..84970a029c --- /dev/null +++ b/common/jobs/job_export_pcb_ipcd356.h @@ -0,0 +1,40 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2025 Connor Goss + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef JOB_EXPORT_PCB_IPCD356_H +#define JOB_EXPORT_PCB_IPCD356_H + +#include +#include +#include "job.h" + +class KICOMMON_API JOB_EXPORT_PCB_IPCD356 : public JOB +{ +public: + JOB_EXPORT_PCB_IPCD356(); + wxString GetDefaultDescription() const override; + wxString GetSettingsDialogTitle() const override; + + void SetDefaultOutputPath( const wxString& aReferenceName ); + + wxString m_filename; +}; + +#endif diff --git a/kicad/CMakeLists.txt b/kicad/CMakeLists.txt index 3a9d7385d8..be03121b73 100644 --- a/kicad/CMakeLists.txt +++ b/kicad/CMakeLists.txt @@ -61,6 +61,7 @@ set( KICAD_CLI_SRCS cli/command_pcb_export_gerbers.cpp cli/command_pcb_export_gencad.cpp cli/command_pcb_export_ipc2581.cpp + cli/command_pcb_export_ipcd356.cpp cli/command_pcb_export_odb.cpp cli/command_pcb_export_pdf.cpp cli/command_pcb_export_pos.cpp diff --git a/kicad/cli/command_pcb_export_ipcd356.cpp b/kicad/cli/command_pcb_export_ipcd356.cpp new file mode 100644 index 0000000000..6545709582 --- /dev/null +++ b/kicad/cli/command_pcb_export_ipcd356.cpp @@ -0,0 +1,60 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2025 Connor Goss + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "command_pcb_export_ipcd356.h" +#include +#include "jobs/job_export_pcb_ipcd356.h" +#include +#include +#include + +#include +#include + +#include + +CLI::PCB_EXPORT_IPCD356_COMMAND::PCB_EXPORT_IPCD356_COMMAND() : PCB_EXPORT_BASE_COMMAND( "ipcd356" ) +{ + m_argParser.add_description( std::string( "Generate IPC-D-356 netlist file" ) ); +} + + +int CLI::PCB_EXPORT_IPCD356_COMMAND::doPerform( KIWAY& aKiway ) +{ + int exitCode = PCB_EXPORT_BASE_COMMAND::doPerform( aKiway ); + + if( exitCode != EXIT_CODES::OK ) + return exitCode; + + std::unique_ptr ipcd356Job( new JOB_EXPORT_PCB_IPCD356() ); + + ipcd356Job->m_filename = m_argInput; + ipcd356Job->SetConfiguredOutputPath( m_argOutput ); + + if( !wxFile::Exists( ipcd356Job->m_filename ) ) + { + wxFprintf( stderr, _( "Board file does not exist or is not accessible\n" ) ); + return EXIT_CODES::ERR_INVALID_INPUT_FILE; + } + + exitCode = aKiway.ProcessJob( KIWAY::FACE_PCB, ipcd356Job.get() ); + + return exitCode; +} diff --git a/kicad/cli/command_pcb_export_ipcd356.h b/kicad/cli/command_pcb_export_ipcd356.h new file mode 100644 index 0000000000..c418d070bb --- /dev/null +++ b/kicad/cli/command_pcb_export_ipcd356.h @@ -0,0 +1,40 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2025 Connor Goss + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef COMMAND_EXPORT_PCB_IPCD356_H +#define COMMAND_EXPORT_PCB_IPCD356_H + +#include "command_pcb_export_base.h" + + +namespace CLI +{ + +class PCB_EXPORT_IPCD356_COMMAND : public PCB_EXPORT_BASE_COMMAND +{ +public: + PCB_EXPORT_IPCD356_COMMAND(); + +protected: + int doPerform( KIWAY& aKiway ) override; +}; +} // namespace CLI + +#endif diff --git a/kicad/kicad_cli.cpp b/kicad/kicad_cli.cpp index 29c937f8d9..883458aa2d 100644 --- a/kicad/kicad_cli.cpp +++ b/kicad/kicad_cli.cpp @@ -59,6 +59,7 @@ #include "cli/command_pcb_export_gerbers.h" #include "cli/command_pcb_export_gencad.h" #include "cli/command_pcb_export_ipc2581.h" +#include "cli/command_pcb_export_ipcd356.h" #include "cli/command_pcb_export_odb.h" #include "cli/command_pcb_export_pdf.h" #include "cli/command_pcb_export_pos.h" @@ -130,6 +131,7 @@ static CLI::PCB_EXPORT_GERBER_COMMAND exportPcbGerberCmd{}; static CLI::PCB_EXPORT_GERBERS_COMMAND exportPcbGerbersCmd{}; static CLI::PCB_EXPORT_GENCAD_COMMAND exportPcbGencadCmd{}; static CLI::PCB_EXPORT_IPC2581_COMMAND exportPcbIpc2581Cmd{}; +static CLI::PCB_EXPORT_IPCD356_COMMAND exportPcbIpcD356Cmd{}; static CLI::PCB_EXPORT_ODB_COMMAND exportPcbOdbCmd{}; static CLI::PCB_EXPORT_COMMAND exportPcbCmd{}; static CLI::SCH_EXPORT_COMMAND exportSchCmd{}; @@ -154,6 +156,7 @@ static CLI::SYM_UPGRADE_COMMAND symUpgradeCmd{}; static CLI::VERSION_COMMAND versionCmd{}; +// clang-format off static std::vector commandStack = { { &jobsetCmd, @@ -197,6 +200,7 @@ static std::vector commandStack = { &exportPcbGencadCmd, &exportPcbGlbCmd, &exportPcbIpc2581Cmd, + &exportPcbIpcD356Cmd, &exportPcbOdbCmd, &exportPcbPdfCmd, &exportPcbPosCmd, @@ -249,6 +253,7 @@ static std::vector commandStack = { &versionCmd, } }; +// clang-format on static void recurseArgParserBuild( argparse::ArgumentParser& aArgParser, COMMAND_ENTRY& aEntry ) diff --git a/pcbnew/exporters/export_d356.cpp b/pcbnew/exporters/export_d356.cpp index d7be20b44f..09f1f6420d 100644 --- a/pcbnew/exporters/export_d356.cpp +++ b/pcbnew/exporters/export_d356.cpp @@ -47,7 +47,7 @@ #include // for KiROUND #include #include - +#include // Compute the access code for a pad. Returns -1 if there is no copper @@ -347,17 +347,14 @@ void IPC356D_WRITER::write_D356_records( std::vector &aRecords, FI } -void IPC356D_WRITER::Write( const wxString& aFilename ) +bool IPC356D_WRITER::Write( const wxString& aFilename ) { FILE* file = nullptr; LOCALE_IO toggle; // Switch the locale to standard C if( ( file = wxFopen( aFilename, wxT( "wt" ) ) ) == nullptr ) { - wxString details; - details.Printf( wxT( "The file %s could not be opened for writing." ), aFilename ); - DisplayErrorMessage( m_parent, wxT( "Could not write IPC-356D file!" ), details ); - return; + return false; } // This will contain everything needed for the 356 file @@ -376,13 +373,15 @@ void IPC356D_WRITER::Write( const wxString& aFilename ) fprintf( file, "999\n" ); fclose( file ); + + return true; } void PCB_EDIT_FRAME::GenD356File( wxCommandEvent& aEvent ) { wxFileName fn = GetBoard()->GetFileName(); - wxString ext, wildcard; + wxString ext, wildcard, msg; ext = FILEEXT::IpcD356FileExtension; wildcard = FILEEXT::IpcD356FileWildcard(); @@ -390,14 +389,24 @@ void PCB_EDIT_FRAME::GenD356File( wxCommandEvent& aEvent ) wxString pro_dir = wxPathOnly( Prj().GetProjectFullName() ); - wxFileDialog dlg( this, _( "Export D-356 Test File" ), pro_dir, - fn.GetFullName(), wildcard, - wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); + wxFileDialog dlg( this, _( "Generate IPC-D-356 netlist file" ), pro_dir, fn.GetFullName(), + wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); if( dlg.ShowModal() == wxID_CANCEL ) return; IPC356D_WRITER writer( GetBoard(), this ); - writer.Write( dlg.GetPath() ); + bool success = writer.Write( dlg.GetPath() ); + + if( success ) + { + msg.Printf( _( "IPC-D-356 netlist file created:\n'%s'." ), dlg.GetPath() ); + wxMessageBox( msg, _( "IPC-D-356 Netlist File" ), wxICON_INFORMATION ); + } + else + { + msg.Printf( _( "Failed to create file '%s'." ), dlg.GetPath() ); + DisplayError( this, msg ); + } } diff --git a/pcbnew/exporters/export_d356.h b/pcbnew/exporters/export_d356.h index 4dea708e5a..900bef4e2f 100644 --- a/pcbnew/exporters/export_d356.h +++ b/pcbnew/exporters/export_d356.h @@ -67,8 +67,9 @@ public: /** * Generates and writes the netlist to a given path * @param aFilename is the full path and name of the output file + * @return true on success */ - void Write( const wxString& aFilename ); + bool Write( const wxString& aFilename ); private: BOARD* m_pcb; diff --git a/pcbnew/pcbnew_jobs_handler.cpp b/pcbnew/pcbnew_jobs_handler.cpp index 2421fc4a99..e2070966be 100644 --- a/pcbnew/pcbnew_jobs_handler.cpp +++ b/pcbnew/pcbnew_jobs_handler.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -62,6 +63,7 @@ #include #include #include +#include #include #include #include @@ -257,6 +259,12 @@ PCBNEW_JOBS_HANDLER::PCBNEW_JOBS_HANDLER( KIWAY* aKiway ) : DIALOG_EXPORT_2581 dlg( ipcJob, editFrame, aParent ); return dlg.ShowModal() == wxID_OK; } ); + Register( "ipcd356", + std::bind( &PCBNEW_JOBS_HANDLER::JobExportIpcD356, this, std::placeholders::_1 ), + []( JOB* job, wxWindow* aParent ) -> bool + { + return true; + } ); Register( "odb", std::bind( &PCBNEW_JOBS_HANDLER::JobExportOdb, this, std::placeholders::_1 ), [aKiway]( JOB* job, wxWindow* aParent ) -> bool @@ -2055,6 +2063,54 @@ int PCBNEW_JOBS_HANDLER::JobExportIpc2581( JOB* aJob ) } +int PCBNEW_JOBS_HANDLER::JobExportIpcD356( JOB* aJob ) +{ + JOB_EXPORT_PCB_IPCD356* job = dynamic_cast( aJob ); + + if( job == nullptr ) + return CLI::EXIT_CODES::ERR_UNKNOWN; + + BOARD* brd = getBoard( job->m_filename ); + + if( !brd ) + return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE; + + aJob->SetTitleBlock( brd->GetTitleBlock() ); + + if( job->GetConfiguredOutputPath().IsEmpty() ) + { + wxFileName fn = brd->GetFileName(); + fn.SetName( fn.GetName() ); + fn.SetExt( FILEEXT::IpcD356FileExtension ); + + job->SetWorkingOutputPath( fn.GetFullName() ); + } + + wxString outPath = job->GetFullOutputPath( brd->GetProject() ); + + if( !PATHS::EnsurePathExists( outPath, true ) ) + { + m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR ); + return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT; + } + + IPC356D_WRITER exporter( brd ); + + bool success = exporter.Write( outPath ); + + if( success ) + { + m_reporter->Report( _( "Successfully created IPC-D-356 file\n" ), RPT_SEVERITY_INFO ); + return CLI::EXIT_CODES::SUCCESS; + } + else + { + m_reporter->Report( _( "Failed to create IPC-D-356 file\n" ), RPT_SEVERITY_ERROR ); + return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT; + } +} + + int PCBNEW_JOBS_HANDLER::JobExportOdb( JOB* aJob ) { JOB_EXPORT_PCB_ODB* job = dynamic_cast( aJob ); diff --git a/pcbnew/pcbnew_jobs_handler.h b/pcbnew/pcbnew_jobs_handler.h index 8e244b1488..974968300b 100644 --- a/pcbnew/pcbnew_jobs_handler.h +++ b/pcbnew/pcbnew_jobs_handler.h @@ -50,6 +50,7 @@ public: int JobExportDrc( JOB* aJob ); int JobExportIpc2581( JOB* aJob ); int JobExportOdb( JOB* aJob ); + int JobExportIpcD356( JOB* aJob ); private: BOARD* getBoard( const wxString& aPath = wxEmptyString );