You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2621 lines
95 KiB

5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
8 months ago
2 years ago
2 years ago
9 months ago
8 months ago
2 years ago
2 years ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
2 years ago
2 years ago
9 months ago
2 years ago
9 months ago
2 years ago
9 months ago
2 years ago
2 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
  5. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software: you can redistribute it and/or modify it
  8. * under the terms of the GNU General Public License as published by the
  9. * Free Software Foundation, either version 3 of the License, or (at your
  10. * option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful, but
  13. * WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License along
  18. * with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. #include <wx/dir.h>
  21. #include "pcbnew_jobs_handler.h"
  22. #include <board_commit.h>
  23. #include <board_design_settings.h>
  24. #include <drc/drc_item.h>
  25. #include <drc/drc_report.h>
  26. #include <drawing_sheet/ds_data_model.h>
  27. #include <drawing_sheet/ds_proxy_view_item.h>
  28. #include <jobs/job_fp_export_svg.h>
  29. #include <jobs/job_fp_upgrade.h>
  30. #include <jobs/job_export_pcb_ipc2581.h>
  31. #include <jobs/job_export_pcb_ipcd356.h>
  32. #include <jobs/job_export_pcb_odb.h>
  33. #include <jobs/job_export_pcb_gerber.h>
  34. #include <jobs/job_export_pcb_gerbers.h>
  35. #include <jobs/job_export_pcb_hpgl.h>
  36. #include <jobs/job_export_pcb_drill.h>
  37. #include <jobs/job_export_pcb_dxf.h>
  38. #include <jobs/job_export_pcb_gencad.h>
  39. #include <jobs/job_export_pcb_pdf.h>
  40. #include <jobs/job_export_pcb_pos.h>
  41. #include <jobs/job_export_pcb_ps.h>
  42. #include <jobs/job_export_pcb_svg.h>
  43. #include <jobs/job_export_pcb_3d.h>
  44. #include <jobs/job_pcb_render.h>
  45. #include <jobs/job_pcb_drc.h>
  46. #include <jobs/job_pcb_upgrade.h>
  47. #include <lset.h>
  48. #include <cli/exit_codes.h>
  49. #include <exporters/place_file_exporter.h>
  50. #include <exporters/step/exporter_step.h>
  51. #include <plotters/plotter_dxf.h>
  52. #include <plotters/plotter_gerber.h>
  53. #include <plotters/plotters_pslike.h>
  54. #include <tool/tool_manager.h>
  55. #include <tools/drc_tool.h>
  56. #include <filename_resolver.h>
  57. #include <gerber_jobfile_writer.h>
  58. #include "gerber_placefile_writer.h"
  59. #include <gendrill_Excellon_writer.h>
  60. #include <gendrill_gerber_writer.h>
  61. #include <kiface_base.h>
  62. #include <macros.h>
  63. #include <pad.h>
  64. #include <pcb_marker.h>
  65. #include <project/project_file.h>
  66. #include <exporters/export_gencad_writer.h>
  67. #include <exporters/export_d356.h>
  68. #include <kiface_ids.h>
  69. #include <netlist_reader/pcb_netlist.h>
  70. #include <netlist_reader/netlist_reader.h>
  71. #include <pcbnew_settings.h>
  72. #include <pcbplot.h>
  73. #include <pcb_plotter.h>
  74. #include <pgm_base.h>
  75. #include <3d_rendering/raytracing/render_3d_raytrace_ram.h>
  76. #include <3d_rendering/track_ball.h>
  77. #include <project_pcb.h>
  78. #include <pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h>
  79. #include <reporter.h>
  80. #include <progress_reporter.h>
  81. #include <wildcards_and_files_ext.h>
  82. #include <export_vrml.h>
  83. #include <wx/wfstream.h>
  84. #include <wx/zipstrm.h>
  85. #include <settings/settings_manager.h>
  86. #include <dialogs/dialog_gendrill.h>
  87. #include <dialogs/dialog_gen_footprint_position.h>
  88. #include <dialogs/dialog_export_2581.h>
  89. #include <dialogs/dialog_export_odbpp.h>
  90. #include <dialogs/dialog_export_step.h>
  91. #include <dialogs/dialog_plot.h>
  92. #include <dialogs/dialog_drc_job_config.h>
  93. #include <dialogs/dialog_render_job.h>
  94. #include <dialogs/dialog_gencad_export_options.h>
  95. #include <paths.h>
  96. #include <tools/zone_filler_tool.h>
  97. #include "pcbnew_scripting_helpers.h"
  98. #include <locale_io.h>
  99. #include <confirm.h>
  100. #ifdef _WIN32
  101. #ifdef TRANSPARENT
  102. #undef TRANSPARENT
  103. #endif
  104. #endif
  105. PCBNEW_JOBS_HANDLER::PCBNEW_JOBS_HANDLER( KIWAY* aKiway ) :
  106. JOB_DISPATCHER( aKiway ),
  107. m_cliBoard( nullptr ),
  108. m_toolManager( nullptr )
  109. {
  110. Register( "3d", std::bind( &PCBNEW_JOBS_HANDLER::JobExportStep, this, std::placeholders::_1 ),
  111. [aKiway]( JOB* job, wxWindow* aParent ) -> bool
  112. {
  113. JOB_EXPORT_PCB_3D* svgJob = dynamic_cast<JOB_EXPORT_PCB_3D*>( job );
  114. PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR,
  115. false ) );
  116. wxCHECK( svgJob && editFrame, false );
  117. DIALOG_EXPORT_STEP dlg( editFrame, aParent, "", svgJob );
  118. return dlg.ShowModal() == wxID_OK;
  119. } );
  120. Register( "render",
  121. std::bind( &PCBNEW_JOBS_HANDLER::JobExportRender, this, std::placeholders::_1 ),
  122. []( JOB* job, wxWindow* aParent ) -> bool
  123. {
  124. JOB_PCB_RENDER* renderJob = dynamic_cast<JOB_PCB_RENDER*>( job );
  125. wxCHECK( renderJob, false );
  126. DIALOG_RENDER_JOB dlg( aParent, renderJob );
  127. return dlg.ShowModal() == wxID_OK;
  128. } );
  129. Register( "upgrade", std::bind( &PCBNEW_JOBS_HANDLER::JobUpgrade, this, std::placeholders::_1 ),
  130. []( JOB* job, wxWindow* aParent ) -> bool
  131. {
  132. return true;
  133. } );
  134. Register( "svg", std::bind( &PCBNEW_JOBS_HANDLER::JobExportSvg, this, std::placeholders::_1 ),
  135. [aKiway]( JOB* job, wxWindow* aParent ) -> bool
  136. {
  137. JOB_EXPORT_PCB_SVG* svgJob = dynamic_cast<JOB_EXPORT_PCB_SVG*>( job );
  138. PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR,
  139. false ) );
  140. wxCHECK( svgJob && editFrame, false );
  141. DIALOG_PLOT dlg( editFrame, aParent, svgJob );
  142. return dlg.ShowModal() == wxID_OK;
  143. } );
  144. Register( "gencad",
  145. std::bind( &PCBNEW_JOBS_HANDLER::JobExportGencad, this, std::placeholders::_1 ),
  146. [aKiway]( JOB* job, wxWindow* aParent ) -> bool
  147. {
  148. JOB_EXPORT_PCB_GENCAD* gencadJob = dynamic_cast<JOB_EXPORT_PCB_GENCAD*>( job );
  149. PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR,
  150. false ) );
  151. wxCHECK( gencadJob && editFrame, false );
  152. DIALOG_GENCAD_EXPORT_OPTIONS dlg( editFrame, gencadJob->GetSettingsDialogTitle(), gencadJob );
  153. return dlg.ShowModal() == wxID_OK;
  154. } );
  155. Register( "dxf", std::bind( &PCBNEW_JOBS_HANDLER::JobExportDxf, this, std::placeholders::_1 ),
  156. [aKiway]( JOB* job, wxWindow* aParent ) -> bool
  157. {
  158. JOB_EXPORT_PCB_DXF* dxfJob = dynamic_cast<JOB_EXPORT_PCB_DXF*>( job );
  159. PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR,
  160. false ) );
  161. wxCHECK( dxfJob && editFrame, false );
  162. DIALOG_PLOT dlg( editFrame, aParent, dxfJob );
  163. return dlg.ShowModal() == wxID_OK;
  164. } );
  165. Register( "pdf", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPdf, this, std::placeholders::_1 ),
  166. [aKiway]( JOB* job, wxWindow* aParent ) -> bool
  167. {
  168. JOB_EXPORT_PCB_PDF* pdfJob = dynamic_cast<JOB_EXPORT_PCB_PDF*>( job );
  169. PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR,
  170. false ) );
  171. wxCHECK( pdfJob && editFrame, false );
  172. DIALOG_PLOT dlg( editFrame, aParent, pdfJob );
  173. return dlg.ShowModal() == wxID_OK;
  174. } );
  175. Register( "ps", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPs, this, std::placeholders::_1 ),
  176. [aKiway]( JOB* job, wxWindow* aParent ) -> bool
  177. {
  178. JOB_EXPORT_PCB_PS* psJob = dynamic_cast<JOB_EXPORT_PCB_PS*>( job );
  179. PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR,
  180. false ) );
  181. wxCHECK( psJob && editFrame, false );
  182. DIALOG_PLOT dlg( editFrame, aParent, psJob );
  183. return dlg.ShowModal() == wxID_OK;
  184. } );
  185. Register( "gerber",
  186. std::bind( &PCBNEW_JOBS_HANDLER::JobExportGerber, this, std::placeholders::_1 ),
  187. [aKiway]( JOB* job, wxWindow* aParent ) -> bool
  188. {
  189. JOB_EXPORT_PCB_GERBER* gJob = dynamic_cast<JOB_EXPORT_PCB_GERBER*>( job );
  190. PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR,
  191. false ) );
  192. wxCHECK( gJob && editFrame, false );
  193. DIALOG_PLOT dlg( editFrame, aParent, gJob );
  194. return dlg.ShowModal() == wxID_OK;
  195. } );
  196. Register( "gerbers",
  197. std::bind( &PCBNEW_JOBS_HANDLER::JobExportGerbers, this, std::placeholders::_1 ),
  198. [aKiway]( JOB* job, wxWindow* aParent ) -> bool
  199. {
  200. JOB_EXPORT_PCB_GERBERS* gJob = dynamic_cast<JOB_EXPORT_PCB_GERBERS*>( job );
  201. PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR,
  202. false ) );
  203. wxCHECK( gJob && editFrame, false );
  204. DIALOG_PLOT dlg( editFrame, aParent, gJob );
  205. return dlg.ShowModal() == wxID_OK;
  206. } );
  207. Register( "hpgl",
  208. [&]( JOB* aJob )
  209. {
  210. m_reporter->Report( _( "Plotting to HPGL is no longer supported as of KiCad 10.0.\n" ),
  211. RPT_SEVERITY_ERROR );
  212. return CLI::EXIT_CODES::ERR_ARGS;
  213. },
  214. [aKiway]( JOB* job, wxWindow* aParent ) -> bool
  215. {
  216. PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR,
  217. false ) );
  218. wxCHECK( editFrame, false );
  219. DisplayErrorMessage( editFrame,
  220. _( "Plotting to HPGL is no longer supported as of KiCad 10.0." ) );
  221. return false;
  222. } );
  223. Register( "drill",
  224. std::bind( &PCBNEW_JOBS_HANDLER::JobExportDrill, this, std::placeholders::_1 ),
  225. [aKiway]( JOB* job, wxWindow* aParent ) -> bool
  226. {
  227. JOB_EXPORT_PCB_DRILL* drillJob = dynamic_cast<JOB_EXPORT_PCB_DRILL*>( job );
  228. PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR,
  229. false ) );
  230. wxCHECK( drillJob && editFrame, false );
  231. DIALOG_GENDRILL dlg( editFrame, drillJob, aParent );
  232. return dlg.ShowModal() == wxID_OK;
  233. } );
  234. Register( "pos", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPos, this, std::placeholders::_1 ),
  235. [aKiway]( JOB* job, wxWindow* aParent ) -> bool
  236. {
  237. JOB_EXPORT_PCB_POS* posJob = dynamic_cast<JOB_EXPORT_PCB_POS*>( job );
  238. PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR,
  239. false ) );
  240. wxCHECK( posJob && editFrame, false );
  241. DIALOG_GEN_FOOTPRINT_POSITION dlg( posJob, editFrame, aParent );
  242. return dlg.ShowModal() == wxID_OK;
  243. } );
  244. Register( "fpupgrade",
  245. std::bind( &PCBNEW_JOBS_HANDLER::JobExportFpUpgrade, this, std::placeholders::_1 ),
  246. []( JOB* job, wxWindow* aParent ) -> bool
  247. {
  248. return true;
  249. } );
  250. Register( "fpsvg",
  251. std::bind( &PCBNEW_JOBS_HANDLER::JobExportFpSvg, this, std::placeholders::_1 ),
  252. []( JOB* job, wxWindow* aParent ) -> bool
  253. {
  254. return true;
  255. } );
  256. Register( "drc", std::bind( &PCBNEW_JOBS_HANDLER::JobExportDrc, this, std::placeholders::_1 ),
  257. []( JOB* job, wxWindow* aParent ) -> bool
  258. {
  259. JOB_PCB_DRC* drcJob = dynamic_cast<JOB_PCB_DRC*>( job );
  260. wxCHECK( drcJob, false );
  261. DIALOG_DRC_JOB_CONFIG dlg( aParent, drcJob );
  262. return dlg.ShowModal() == wxID_OK;
  263. } );
  264. Register( "ipc2581",
  265. std::bind( &PCBNEW_JOBS_HANDLER::JobExportIpc2581, this, std::placeholders::_1 ),
  266. [aKiway]( JOB* job, wxWindow* aParent ) -> bool
  267. {
  268. JOB_EXPORT_PCB_IPC2581* ipcJob = dynamic_cast<JOB_EXPORT_PCB_IPC2581*>( job );
  269. PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR,
  270. false ) );
  271. wxCHECK( ipcJob && editFrame, false );
  272. DIALOG_EXPORT_2581 dlg( ipcJob, editFrame, aParent );
  273. return dlg.ShowModal() == wxID_OK;
  274. } );
  275. Register( "ipcd356",
  276. std::bind( &PCBNEW_JOBS_HANDLER::JobExportIpcD356, this, std::placeholders::_1 ),
  277. []( JOB* job, wxWindow* aParent ) -> bool
  278. {
  279. return true;
  280. } );
  281. Register( "odb",
  282. std::bind( &PCBNEW_JOBS_HANDLER::JobExportOdb, this, std::placeholders::_1 ),
  283. [aKiway]( JOB* job, wxWindow* aParent ) -> bool
  284. {
  285. JOB_EXPORT_PCB_ODB* odbJob = dynamic_cast<JOB_EXPORT_PCB_ODB*>( job );
  286. PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR,
  287. false ) );
  288. wxCHECK( odbJob && editFrame, false );
  289. DIALOG_EXPORT_ODBPP dlg( odbJob, editFrame, aParent );
  290. return dlg.ShowModal() == wxID_OK;
  291. } );
  292. }
  293. PCBNEW_JOBS_HANDLER::~PCBNEW_JOBS_HANDLER()
  294. {
  295. }
  296. TOOL_MANAGER* PCBNEW_JOBS_HANDLER::getToolManager( BOARD* aBrd )
  297. {
  298. TOOL_MANAGER* toolManager = nullptr;
  299. if( Pgm().IsGUI() )
  300. {
  301. // we assume the PCB we are working on here is the one in the frame
  302. // so use the frame's tool manager
  303. PCB_EDIT_FRAME* editFrame = (PCB_EDIT_FRAME*) m_kiway->Player( FRAME_PCB_EDITOR, false );
  304. if( editFrame )
  305. toolManager = editFrame->GetToolManager();
  306. }
  307. else
  308. {
  309. if( m_toolManager == nullptr )
  310. {
  311. m_toolManager = std::make_unique<TOOL_MANAGER>();
  312. }
  313. toolManager = m_toolManager.get();
  314. toolManager->SetEnvironment( aBrd, nullptr, nullptr, Kiface().KifaceSettings(), nullptr );
  315. }
  316. return toolManager;
  317. }
  318. BOARD* PCBNEW_JOBS_HANDLER::getBoard( const wxString& aPath )
  319. {
  320. BOARD* brd = nullptr;
  321. if( !Pgm().IsGUI() && Pgm().GetSettingsManager().IsProjectOpen() )
  322. {
  323. wxString pcbPath = aPath;
  324. if( pcbPath.IsEmpty() )
  325. {
  326. wxFileName path = Pgm().GetSettingsManager().Prj().GetProjectFullName();
  327. path.SetExt( FILEEXT::KiCadPcbFileExtension );
  328. path.MakeAbsolute();
  329. pcbPath = path.GetFullPath();
  330. }
  331. if( !m_cliBoard )
  332. m_cliBoard = LoadBoard( pcbPath, true );
  333. brd = m_cliBoard;
  334. }
  335. else if( Pgm().IsGUI() && Pgm().GetSettingsManager().IsProjectOpen() )
  336. {
  337. PCB_EDIT_FRAME* editFrame = (PCB_EDIT_FRAME*) m_kiway->Player( FRAME_PCB_EDITOR, false );
  338. if( editFrame )
  339. brd = editFrame->GetBoard();
  340. }
  341. else
  342. {
  343. brd = LoadBoard( aPath, true );
  344. }
  345. if( !brd )
  346. m_reporter->Report( _( "Failed to load board\n" ), RPT_SEVERITY_ERROR );
  347. return brd;
  348. }
  349. LSEQ PCBNEW_JOBS_HANDLER::convertLayerArg( wxString& aLayerString, BOARD* aBoard ) const
  350. {
  351. std::map<wxString, LSET> layerUserMasks;
  352. std::map<wxString, LSET> layerMasks;
  353. std::map<wxString, LSET> layerGuiMasks;
  354. // Build list of layer names and their layer mask:
  355. for( PCB_LAYER_ID layer : LSET::AllLayersMask() )
  356. {
  357. // Add user layer name
  358. if( aBoard )
  359. layerUserMasks[ aBoard->GetLayerName( layer ) ] = LSET( { layer } );
  360. // Add layer name used in pcb files
  361. layerMasks[ LSET::Name( layer ) ] = LSET( { layer } );
  362. // Add layer name using GUI canonical layer name
  363. layerGuiMasks[ LayerName( layer ) ] = LSET( { layer } );
  364. }
  365. // Add list of grouped layer names used in pcb files
  366. layerMasks[ wxT( "*" ) ] = LSET::AllLayersMask();
  367. layerMasks[ wxT( "*.Cu" ) ] = LSET::AllCuMask();
  368. layerMasks[ wxT( "*In.Cu" ) ] = LSET::InternalCuMask();
  369. layerMasks[ wxT( "F&B.Cu" ) ] = LSET( { F_Cu, B_Cu } );
  370. layerMasks[ wxT( "*.Adhes" ) ] = LSET( { B_Adhes, F_Adhes } );
  371. layerMasks[ wxT( "*.Paste" ) ] = LSET( { B_Paste, F_Paste } );
  372. layerMasks[ wxT( "*.Mask" ) ] = LSET( { B_Mask, F_Mask } );
  373. layerMasks[ wxT( "*.SilkS" ) ] = LSET( { B_SilkS, F_SilkS } );
  374. layerMasks[ wxT( "*.Fab" ) ] = LSET( { B_Fab, F_Fab } );
  375. layerMasks[ wxT( "*.CrtYd" ) ] = LSET( { B_CrtYd, F_CrtYd } );
  376. // Add list of grouped layer names using GUI canonical layer names
  377. layerGuiMasks[ wxT( "*.Adhesive" ) ] = LSET( { B_Adhes, F_Adhes } );
  378. layerGuiMasks[ wxT( "*.Silkscreen" ) ] = LSET( { B_SilkS, F_SilkS } );
  379. layerGuiMasks[ wxT( "*.Courtyard" ) ] = LSET( { B_CrtYd, F_CrtYd } );
  380. LSEQ layerMask;
  381. auto pushLayers =
  382. [&]( const LSET& layerSet )
  383. {
  384. for( PCB_LAYER_ID layer : layerSet.Seq() )
  385. layerMask.push_back( layer );
  386. };
  387. if( !aLayerString.IsEmpty() )
  388. {
  389. wxStringTokenizer layerTokens( aLayerString, "," );
  390. while( layerTokens.HasMoreTokens() )
  391. {
  392. std::string token = TO_UTF8( layerTokens.GetNextToken().Trim( true ).Trim( false ) );
  393. if( layerUserMasks.contains( token ) )
  394. pushLayers( layerUserMasks.at( token ) );
  395. else if( layerMasks.count( token ) )
  396. pushLayers( layerMasks.at( token ) );
  397. else if( layerGuiMasks.count( token ) )
  398. pushLayers( layerGuiMasks.at( token ) );
  399. else
  400. m_reporter->Report( wxString::Format( _( "Invalid layer name '%s'\n" ), token ) );
  401. }
  402. }
  403. return layerMask;
  404. }
  405. int PCBNEW_JOBS_HANDLER::JobExportStep( JOB* aJob )
  406. {
  407. JOB_EXPORT_PCB_3D* aStepJob = dynamic_cast<JOB_EXPORT_PCB_3D*>( aJob );
  408. if( aStepJob == nullptr )
  409. return CLI::EXIT_CODES::ERR_UNKNOWN;
  410. BOARD* brd = getBoard( aStepJob->m_filename );
  411. if( !brd )
  412. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  413. aJob->SetTitleBlock( brd->GetTitleBlock() );
  414. brd->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
  415. brd->SynchronizeProperties();
  416. if( aStepJob->GetConfiguredOutputPath().IsEmpty() )
  417. {
  418. wxFileName fn = brd->GetFileName();
  419. fn.SetName( fn.GetName() );
  420. switch( aStepJob->m_format )
  421. {
  422. case JOB_EXPORT_PCB_3D::FORMAT::VRML: fn.SetExt( FILEEXT::VrmlFileExtension ); break;
  423. case JOB_EXPORT_PCB_3D::FORMAT::STEP: fn.SetExt( FILEEXT::StepFileExtension ); break;
  424. case JOB_EXPORT_PCB_3D::FORMAT::BREP: fn.SetExt( FILEEXT::BrepFileExtension ); break;
  425. case JOB_EXPORT_PCB_3D::FORMAT::XAO: fn.SetExt( FILEEXT::XaoFileExtension ); break;
  426. case JOB_EXPORT_PCB_3D::FORMAT::GLB: fn.SetExt( FILEEXT::GltfBinaryFileExtension ); break;
  427. case JOB_EXPORT_PCB_3D::FORMAT::PLY: fn.SetExt( FILEEXT::PlyFileExtension ); break;
  428. case JOB_EXPORT_PCB_3D::FORMAT::STL: fn.SetExt( FILEEXT::StlFileExtension ); break;
  429. case JOB_EXPORT_PCB_3D::FORMAT::U3D: fn.SetExt( FILEEXT::U3DFileExtension ); break;
  430. case JOB_EXPORT_PCB_3D::FORMAT::PDF: fn.SetExt( FILEEXT::PdfFileExtension ); break;
  431. default:
  432. m_reporter->Report( _( "Unknown export format" ), RPT_SEVERITY_ERROR );
  433. return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
  434. }
  435. aStepJob->SetWorkingOutputPath( fn.GetFullName() );
  436. }
  437. wxString outPath = aStepJob->GetFullOutputPath( brd->GetProject() );
  438. if( !PATHS::EnsurePathExists( outPath, true ) )
  439. {
  440. m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
  441. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  442. }
  443. if( aStepJob->m_format == JOB_EXPORT_PCB_3D::FORMAT::VRML )
  444. {
  445. double scale = 0.0;
  446. switch ( aStepJob->m_vrmlUnits )
  447. {
  448. case JOB_EXPORT_PCB_3D::VRML_UNITS::MM: scale = 1.0; break;
  449. case JOB_EXPORT_PCB_3D::VRML_UNITS::METERS: scale = 0.001; break;
  450. case JOB_EXPORT_PCB_3D::VRML_UNITS::TENTHS: scale = 10.0 / 25.4; break;
  451. case JOB_EXPORT_PCB_3D::VRML_UNITS::INCH: scale = 1.0 / 25.4; break;
  452. }
  453. EXPORTER_VRML vrmlExporter( brd );
  454. wxString messages;
  455. double originX = pcbIUScale.IUTomm( aStepJob->m_3dparams.m_Origin.x );
  456. double originY = pcbIUScale.IUTomm( aStepJob->m_3dparams.m_Origin.y );
  457. if( !aStepJob->m_hasUserOrigin )
  458. {
  459. BOX2I bbox = brd->ComputeBoundingBox( true );
  460. originX = pcbIUScale.IUTomm( bbox.GetCenter().x );
  461. originY = pcbIUScale.IUTomm( bbox.GetCenter().y );
  462. }
  463. bool success = vrmlExporter.ExportVRML_File( brd->GetProject(),
  464. &messages,
  465. outPath,
  466. scale,
  467. aStepJob->m_3dparams.m_IncludeUnspecified,
  468. aStepJob->m_3dparams.m_IncludeDNP,
  469. !aStepJob->m_vrmlModelDir.IsEmpty(),
  470. aStepJob->m_vrmlRelativePaths,
  471. aStepJob->m_vrmlModelDir,
  472. originX,
  473. originY );
  474. if ( success )
  475. {
  476. m_reporter->Report( wxString::Format( _( "Successfully exported VRML to %s" ),
  477. outPath ),
  478. RPT_SEVERITY_INFO );
  479. }
  480. else
  481. {
  482. m_reporter->Report( _( "Error exporting VRML" ), RPT_SEVERITY_ERROR );
  483. return CLI::EXIT_CODES::ERR_UNKNOWN;
  484. }
  485. }
  486. else
  487. {
  488. EXPORTER_STEP_PARAMS params = aStepJob->m_3dparams;
  489. switch( aStepJob->m_format )
  490. {
  491. case JOB_EXPORT_PCB_3D::FORMAT::STEP: params.m_Format = EXPORTER_STEP_PARAMS::FORMAT::STEP; break;
  492. case JOB_EXPORT_PCB_3D::FORMAT::STEPZ: params.m_Format = EXPORTER_STEP_PARAMS::FORMAT::STEPZ; break;
  493. case JOB_EXPORT_PCB_3D::FORMAT::BREP: params.m_Format = EXPORTER_STEP_PARAMS::FORMAT::BREP; break;
  494. case JOB_EXPORT_PCB_3D::FORMAT::XAO: params.m_Format = EXPORTER_STEP_PARAMS::FORMAT::XAO; break;
  495. case JOB_EXPORT_PCB_3D::FORMAT::GLB: params.m_Format = EXPORTER_STEP_PARAMS::FORMAT::GLB; break;
  496. case JOB_EXPORT_PCB_3D::FORMAT::PLY: params.m_Format = EXPORTER_STEP_PARAMS::FORMAT::PLY; break;
  497. case JOB_EXPORT_PCB_3D::FORMAT::STL: params.m_Format = EXPORTER_STEP_PARAMS::FORMAT::STL; break;
  498. case JOB_EXPORT_PCB_3D::FORMAT::U3D: params.m_Format = EXPORTER_STEP_PARAMS::FORMAT::U3D; break;
  499. case JOB_EXPORT_PCB_3D::FORMAT::PDF: params.m_Format = EXPORTER_STEP_PARAMS::FORMAT::PDF; break;
  500. default:
  501. m_reporter->Report( _( "Unknown export format" ), RPT_SEVERITY_ERROR );
  502. return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
  503. }
  504. EXPORTER_STEP stepExporter( brd, params, m_reporter );
  505. stepExporter.m_outputFile = aStepJob->GetFullOutputPath( brd->GetProject() );
  506. if( !stepExporter.Export() )
  507. return CLI::EXIT_CODES::ERR_UNKNOWN;
  508. }
  509. return CLI::EXIT_CODES::OK;
  510. }
  511. int PCBNEW_JOBS_HANDLER::JobExportRender( JOB* aJob )
  512. {
  513. JOB_PCB_RENDER* aRenderJob = dynamic_cast<JOB_PCB_RENDER*>( aJob );
  514. if( aRenderJob == nullptr )
  515. return CLI::EXIT_CODES::ERR_UNKNOWN;
  516. // Reject width and height being invalid
  517. // Final bit of sanity because this can blow things up
  518. if( aRenderJob->m_width <= 0 || aRenderJob->m_height <= 0 )
  519. {
  520. m_reporter->Report( _( "Invalid image dimensions" ), RPT_SEVERITY_ERROR );
  521. return CLI::EXIT_CODES::ERR_ARGS;
  522. }
  523. BOARD* brd = getBoard( aRenderJob->m_filename );
  524. if( !brd )
  525. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  526. aJob->SetTitleBlock( brd->GetTitleBlock() );
  527. brd->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
  528. brd->SynchronizeProperties();
  529. if( aRenderJob->GetConfiguredOutputPath().IsEmpty() )
  530. {
  531. wxFileName fn = brd->GetFileName();
  532. switch( aRenderJob->m_format )
  533. {
  534. case JOB_PCB_RENDER::FORMAT::JPEG: fn.SetExt( FILEEXT::JpegFileExtension ); break;
  535. case JOB_PCB_RENDER::FORMAT::PNG: fn.SetExt( FILEEXT::PngFileExtension ); break;
  536. default:
  537. m_reporter->Report( _( "Unknown export format" ), RPT_SEVERITY_ERROR );
  538. return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
  539. }
  540. // set the name to board name + "side", its lazy but its hard to generate anything truely unique
  541. // incase someone is doing this in a jobset with multiple jobs, they should be setting the output themselves
  542. // or we do a hash based on all the options
  543. fn.SetName( wxString::Format( "%s-%d", fn.GetName(), static_cast<int>( aRenderJob->m_side ) ) );
  544. aRenderJob->SetWorkingOutputPath( fn.GetFullName() );
  545. }
  546. wxString outPath = aRenderJob->GetFullOutputPath( brd->GetProject() );
  547. if( !PATHS::EnsurePathExists( outPath, true ) )
  548. {
  549. m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
  550. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  551. }
  552. BOARD_ADAPTER boardAdapter;
  553. boardAdapter.SetBoard( brd );
  554. boardAdapter.m_IsBoardView = false;
  555. EDA_3D_VIEWER_SETTINGS cfg;
  556. if( EDA_3D_VIEWER_SETTINGS* userCfg = GetAppSettings<EDA_3D_VIEWER_SETTINGS>( "3d_viewer" ) )
  557. {
  558. cfg.m_Render = userCfg->m_Render;
  559. cfg.m_Camera = userCfg->m_Camera;
  560. cfg.m_LayerPresets = userCfg->m_LayerPresets;
  561. }
  562. if( aRenderJob->m_appearancePreset.empty() )
  563. {
  564. // Force display 3D models
  565. cfg.m_Render.show_footprints_not_in_posfile = true;
  566. cfg.m_Render.show_footprints_dnp = true;
  567. cfg.m_Render.show_footprints_insert = true;
  568. cfg.m_Render.show_footprints_normal = true;
  569. cfg.m_Render.show_footprints_virtual = true;
  570. }
  571. if( aRenderJob->m_quality == JOB_PCB_RENDER::QUALITY::BASIC )
  572. {
  573. // Silkscreen is pixelated without antialiasing
  574. cfg.m_Render.raytrace_anti_aliasing = true;
  575. cfg.m_Render.raytrace_backfloor = aRenderJob->m_floor;
  576. cfg.m_Render.raytrace_post_processing = aRenderJob->m_floor;
  577. cfg.m_Render.raytrace_procedural_textures = false;
  578. cfg.m_Render.raytrace_reflections = false;
  579. cfg.m_Render.raytrace_shadows = aRenderJob->m_floor;
  580. // Better colors
  581. cfg.m_Render.differentiate_plated_copper = true;
  582. // Tracks below soldermask are not visible without refractions
  583. cfg.m_Render.raytrace_refractions = true;
  584. cfg.m_Render.raytrace_recursivelevel_refractions = 1;
  585. }
  586. else if( aRenderJob->m_quality == JOB_PCB_RENDER::QUALITY::HIGH )
  587. {
  588. cfg.m_Render.raytrace_anti_aliasing = true;
  589. cfg.m_Render.raytrace_backfloor = true;
  590. cfg.m_Render.raytrace_post_processing = true;
  591. cfg.m_Render.raytrace_procedural_textures = true;
  592. cfg.m_Render.raytrace_reflections = true;
  593. cfg.m_Render.raytrace_shadows = true;
  594. cfg.m_Render.raytrace_refractions = true;
  595. cfg.m_Render.differentiate_plated_copper = true;
  596. }
  597. else if( aRenderJob->m_quality == JOB_PCB_RENDER::QUALITY::JOB_SETTINGS )
  598. {
  599. cfg.m_Render.raytrace_anti_aliasing = aRenderJob->m_antiAlias;
  600. cfg.m_Render.raytrace_backfloor = aRenderJob->m_floor;
  601. cfg.m_Render.raytrace_post_processing = aRenderJob->m_postProcess;
  602. cfg.m_Render.raytrace_procedural_textures = aRenderJob->m_proceduralTextures;
  603. }
  604. cfg.m_Render.raytrace_lightColorTop = COLOR4D( aRenderJob->m_lightTopIntensity.x,
  605. aRenderJob->m_lightTopIntensity.y,
  606. aRenderJob->m_lightTopIntensity.z, 1.0 );
  607. cfg.m_Render.raytrace_lightColorBottom = COLOR4D( aRenderJob->m_lightBottomIntensity.x,
  608. aRenderJob->m_lightBottomIntensity.y,
  609. aRenderJob->m_lightBottomIntensity.z, 1.0 );
  610. cfg.m_Render.raytrace_lightColorCamera = COLOR4D( aRenderJob->m_lightCameraIntensity.x,
  611. aRenderJob->m_lightCameraIntensity.y,
  612. aRenderJob->m_lightCameraIntensity.z, 1.0 );
  613. COLOR4D lightColor( aRenderJob->m_lightSideIntensity.x,
  614. aRenderJob->m_lightSideIntensity.y,
  615. aRenderJob->m_lightSideIntensity.z, 1.0 );
  616. cfg.m_Render.raytrace_lightColor = {
  617. lightColor, lightColor, lightColor, lightColor,
  618. lightColor, lightColor, lightColor, lightColor,
  619. };
  620. int sideElevation = aRenderJob->m_lightSideElevation;
  621. cfg.m_Render.raytrace_lightElevation = {
  622. sideElevation, sideElevation, sideElevation, sideElevation,
  623. -sideElevation, -sideElevation, -sideElevation, -sideElevation,
  624. };
  625. cfg.m_Render.raytrace_lightAzimuth = {
  626. 45, 135, 225, 315, 45, 135, 225, 315,
  627. };
  628. cfg.m_CurrentPreset = aRenderJob->m_appearancePreset;
  629. cfg.m_UseStackupColors = aRenderJob->m_useBoardStackupColors;
  630. boardAdapter.m_Cfg = &cfg;
  631. if( aRenderJob->m_bgStyle == JOB_PCB_RENDER::BG_STYLE::TRANSPARENT
  632. || ( aRenderJob->m_bgStyle == JOB_PCB_RENDER::BG_STYLE::DEFAULT
  633. && aRenderJob->m_format == JOB_PCB_RENDER::FORMAT::PNG ) )
  634. {
  635. boardAdapter.m_ColorOverrides[LAYER_3D_BACKGROUND_TOP] = COLOR4D( 1.0, 1.0, 1.0, 0.0 );
  636. boardAdapter.m_ColorOverrides[LAYER_3D_BACKGROUND_BOTTOM] = COLOR4D( 1.0, 1.0, 1.0, 0.0 );
  637. }
  638. boardAdapter.Set3dCacheManager( PROJECT_PCB::Get3DCacheManager( brd->GetProject() ) );
  639. static std::map<JOB_PCB_RENDER::SIDE, VIEW3D_TYPE> s_viewCmdMap = {
  640. { JOB_PCB_RENDER::SIDE::TOP, VIEW3D_TYPE::VIEW3D_TOP },
  641. { JOB_PCB_RENDER::SIDE::BOTTOM, VIEW3D_TYPE::VIEW3D_BOTTOM },
  642. { JOB_PCB_RENDER::SIDE::LEFT, VIEW3D_TYPE::VIEW3D_LEFT },
  643. { JOB_PCB_RENDER::SIDE::RIGHT, VIEW3D_TYPE::VIEW3D_RIGHT },
  644. { JOB_PCB_RENDER::SIDE::FRONT, VIEW3D_TYPE::VIEW3D_FRONT },
  645. { JOB_PCB_RENDER::SIDE::BACK, VIEW3D_TYPE::VIEW3D_BACK },
  646. };
  647. PROJECTION_TYPE projection = aRenderJob->m_perspective ? PROJECTION_TYPE::PERSPECTIVE
  648. : PROJECTION_TYPE::ORTHO;
  649. wxSize windowSize( aRenderJob->m_width, aRenderJob->m_height );
  650. TRACK_BALL camera( 2 * RANGE_SCALE_3D );
  651. camera.SetProjection( projection );
  652. camera.SetCurWindowSize( windowSize );
  653. RENDER_3D_RAYTRACE_RAM raytrace( boardAdapter, camera );
  654. raytrace.SetCurWindowSize( windowSize );
  655. for( bool first = true; raytrace.Redraw( false, m_reporter, m_reporter ); first = false )
  656. {
  657. if( first )
  658. {
  659. const float cmTo3D = boardAdapter.BiuTo3dUnits() * pcbIUScale.mmToIU( 10.0 );
  660. // First redraw resets lookat point to the board center, so set up the camera here
  661. camera.ViewCommand_T1( s_viewCmdMap[aRenderJob->m_side] );
  662. camera.SetLookAtPos_T1( camera.GetLookAtPos_T1() + SFVEC3F( aRenderJob->m_pivot.x,
  663. aRenderJob->m_pivot.y,
  664. aRenderJob->m_pivot.z ) * cmTo3D );
  665. camera.Pan_T1( SFVEC3F( aRenderJob->m_pan.x, aRenderJob->m_pan.y, aRenderJob->m_pan.z ) );
  666. camera.Zoom_T1( aRenderJob->m_zoom );
  667. camera.RotateX_T1( DEG2RAD( aRenderJob->m_rotation.x ) );
  668. camera.RotateY_T1( DEG2RAD( aRenderJob->m_rotation.y ) );
  669. camera.RotateZ_T1( DEG2RAD( aRenderJob->m_rotation.z ) );
  670. camera.Interpolate( 1.0f );
  671. camera.SetT0_and_T1_current_T();
  672. camera.ParametersChanged();
  673. }
  674. }
  675. uint8_t* rgbaBuffer = raytrace.GetBuffer();
  676. wxSize realSize = raytrace.GetRealBufferSize();
  677. bool success = !!rgbaBuffer;
  678. if( rgbaBuffer )
  679. {
  680. const unsigned int wxh = realSize.x * realSize.y;
  681. unsigned char* rgbBuffer = (unsigned char*) malloc( wxh * 3 );
  682. unsigned char* alphaBuffer = (unsigned char*) malloc( wxh );
  683. unsigned char* rgbaPtr = rgbaBuffer;
  684. unsigned char* rgbPtr = rgbBuffer;
  685. unsigned char* alphaPtr = alphaBuffer;
  686. for( int y = 0; y < realSize.y; y++ )
  687. {
  688. for( int x = 0; x < realSize.x; x++ )
  689. {
  690. rgbPtr[0] = rgbaPtr[0];
  691. rgbPtr[1] = rgbaPtr[1];
  692. rgbPtr[2] = rgbaPtr[2];
  693. alphaPtr[0] = rgbaPtr[3];
  694. rgbaPtr += 4;
  695. rgbPtr += 3;
  696. alphaPtr += 1;
  697. }
  698. }
  699. wxImage image( realSize );
  700. image.SetData( rgbBuffer );
  701. image.SetAlpha( alphaBuffer );
  702. image = image.Mirror( false );
  703. image.SetOption( wxIMAGE_OPTION_QUALITY, 90 );
  704. image.SaveFile( outPath, aRenderJob->m_format == JOB_PCB_RENDER::FORMAT::PNG ? wxBITMAP_TYPE_PNG
  705. : wxBITMAP_TYPE_JPEG );
  706. }
  707. if( success )
  708. {
  709. m_reporter->Report( _( "Successfully created 3D render image" ) + wxS( "\n" ), RPT_SEVERITY_INFO );
  710. return CLI::EXIT_CODES::OK;
  711. }
  712. else
  713. {
  714. m_reporter->Report( _( "Error creating 3D render image" ) + wxS( "\n" ), RPT_SEVERITY_ERROR );
  715. return CLI::EXIT_CODES::ERR_UNKNOWN;
  716. }
  717. }
  718. int PCBNEW_JOBS_HANDLER::JobExportSvg( JOB* aJob )
  719. {
  720. JOB_EXPORT_PCB_SVG* aSvgJob = dynamic_cast<JOB_EXPORT_PCB_SVG*>( aJob );
  721. if( aSvgJob == nullptr )
  722. return CLI::EXIT_CODES::ERR_UNKNOWN;
  723. BOARD* brd = getBoard( aSvgJob->m_filename );
  724. TOOL_MANAGER* toolManager = getToolManager( brd );
  725. if( !brd )
  726. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  727. aJob->SetTitleBlock( brd->GetTitleBlock() );
  728. if( aSvgJob->m_genMode == JOB_EXPORT_PCB_SVG::GEN_MODE::SINGLE )
  729. {
  730. if( aSvgJob->GetConfiguredOutputPath().IsEmpty() )
  731. {
  732. wxFileName fn = brd->GetFileName();
  733. fn.SetName( fn.GetName() );
  734. fn.SetExt( GetDefaultPlotExtension( PLOT_FORMAT::SVG ) );
  735. aSvgJob->SetWorkingOutputPath( fn.GetFullName() );
  736. }
  737. }
  738. wxString outPath = aSvgJob->GetFullOutputPath( brd->GetProject() );
  739. if( !PATHS::EnsurePathExists( outPath, aSvgJob->m_genMode == JOB_EXPORT_PCB_SVG::GEN_MODE::SINGLE ) )
  740. {
  741. m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
  742. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  743. }
  744. loadOverrideDrawingSheet( brd, aSvgJob->m_drawingSheet );
  745. brd->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
  746. brd->SynchronizeProperties();
  747. if( aSvgJob->m_checkZonesBeforePlot )
  748. {
  749. if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
  750. toolManager->RegisterTool( new ZONE_FILLER_TOOL );
  751. toolManager->GetTool<ZONE_FILLER_TOOL>()->CheckAllZones( nullptr );
  752. }
  753. if( aSvgJob->m_argLayers )
  754. aSvgJob->m_plotLayerSequence = convertLayerArg( aSvgJob->m_argLayers.value(), brd );
  755. if( aSvgJob->m_argCommonLayers )
  756. aSvgJob->m_plotOnAllLayersSequence = convertLayerArg( aSvgJob->m_argCommonLayers.value(), brd );
  757. if( aSvgJob->m_plotLayerSequence.size() < 1 )
  758. {
  759. m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
  760. return CLI::EXIT_CODES::ERR_ARGS;
  761. }
  762. PCB_PLOT_PARAMS plotOpts;
  763. PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aSvgJob, *m_reporter );
  764. PCB_PLOTTER plotter( brd, m_reporter, plotOpts );
  765. std::optional<wxString> layerName;
  766. std::optional<wxString> sheetName;
  767. std::optional<wxString> sheetPath;
  768. if( aSvgJob->m_genMode == JOB_EXPORT_PCB_SVG::GEN_MODE::SINGLE )
  769. {
  770. if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
  771. layerName = aSvgJob->GetVarOverrides().at( wxT( "LAYER" ) );
  772. if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
  773. sheetName = aSvgJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
  774. if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
  775. sheetPath = aSvgJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
  776. }
  777. if( !plotter.Plot( outPath, aSvgJob->m_plotLayerSequence, aSvgJob->m_plotOnAllLayersSequence,
  778. false, aSvgJob->m_genMode == JOB_EXPORT_PCB_SVG::GEN_MODE::SINGLE,
  779. layerName, sheetName, sheetPath ) )
  780. {
  781. return CLI::EXIT_CODES::ERR_UNKNOWN;
  782. }
  783. return CLI::EXIT_CODES::OK;
  784. }
  785. int PCBNEW_JOBS_HANDLER::JobExportDxf( JOB* aJob )
  786. {
  787. JOB_EXPORT_PCB_DXF* aDxfJob = dynamic_cast<JOB_EXPORT_PCB_DXF*>( aJob );
  788. if( aDxfJob == nullptr )
  789. return CLI::EXIT_CODES::ERR_UNKNOWN;
  790. BOARD* brd = getBoard( aDxfJob->m_filename );
  791. if( !brd )
  792. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  793. TOOL_MANAGER* toolManager = getToolManager( brd );
  794. aJob->SetTitleBlock( brd->GetTitleBlock() );
  795. loadOverrideDrawingSheet( brd, aDxfJob->m_drawingSheet );
  796. brd->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
  797. brd->SynchronizeProperties();
  798. if( aDxfJob->m_checkZonesBeforePlot )
  799. {
  800. if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
  801. toolManager->RegisterTool( new ZONE_FILLER_TOOL );
  802. toolManager->GetTool<ZONE_FILLER_TOOL>()->CheckAllZones( nullptr );
  803. }
  804. if( aDxfJob->m_argLayers )
  805. aDxfJob->m_plotLayerSequence = convertLayerArg( aDxfJob->m_argLayers.value(), brd );
  806. if( aDxfJob->m_argCommonLayers )
  807. aDxfJob->m_plotOnAllLayersSequence = convertLayerArg( aDxfJob->m_argCommonLayers.value(), brd );
  808. if( aDxfJob->m_plotLayerSequence.size() < 1 )
  809. {
  810. m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
  811. return CLI::EXIT_CODES::ERR_ARGS;
  812. }
  813. if( aDxfJob->m_genMode == JOB_EXPORT_PCB_DXF::GEN_MODE::SINGLE )
  814. {
  815. if( aDxfJob->GetConfiguredOutputPath().IsEmpty() )
  816. {
  817. wxFileName fn = brd->GetFileName();
  818. fn.SetName( fn.GetName() );
  819. fn.SetExt( GetDefaultPlotExtension( PLOT_FORMAT::DXF ) );
  820. aDxfJob->SetWorkingOutputPath( fn.GetFullName() );
  821. }
  822. }
  823. wxString outPath = aDxfJob->GetFullOutputPath( brd->GetProject() );
  824. if( !PATHS::EnsurePathExists( outPath, aDxfJob->m_genMode == JOB_EXPORT_PCB_DXF::GEN_MODE::SINGLE ) )
  825. {
  826. m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
  827. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  828. }
  829. PCB_PLOT_PARAMS plotOpts;
  830. PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aDxfJob, *m_reporter);
  831. PCB_PLOTTER plotter( brd, m_reporter, plotOpts );
  832. std::optional<wxString> layerName;
  833. std::optional<wxString> sheetName;
  834. std::optional<wxString> sheetPath;
  835. if( aDxfJob->m_genMode == JOB_EXPORT_PCB_DXF::GEN_MODE::SINGLE )
  836. {
  837. if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
  838. layerName = aDxfJob->GetVarOverrides().at( wxT( "LAYER" ) );
  839. if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
  840. sheetName = aDxfJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
  841. if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
  842. sheetPath = aDxfJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
  843. }
  844. if( !plotter.Plot( outPath, aDxfJob->m_plotLayerSequence, aDxfJob->m_plotOnAllLayersSequence,
  845. false, aDxfJob->m_genMode == JOB_EXPORT_PCB_DXF::GEN_MODE::SINGLE,
  846. layerName, sheetName, sheetPath ) )
  847. {
  848. return CLI::EXIT_CODES::ERR_UNKNOWN;
  849. }
  850. return CLI::EXIT_CODES::OK;
  851. }
  852. int PCBNEW_JOBS_HANDLER::JobExportPdf( JOB* aJob )
  853. {
  854. bool plotAllLayersOneFile = false;
  855. JOB_EXPORT_PCB_PDF* pdfJob = dynamic_cast<JOB_EXPORT_PCB_PDF*>( aJob );
  856. if( pdfJob == nullptr )
  857. return CLI::EXIT_CODES::ERR_UNKNOWN;
  858. BOARD* brd = getBoard( pdfJob->m_filename );
  859. if( !brd )
  860. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  861. TOOL_MANAGER* toolManager = getToolManager( brd );
  862. pdfJob->SetTitleBlock( brd->GetTitleBlock() );
  863. loadOverrideDrawingSheet( brd, pdfJob->m_drawingSheet );
  864. brd->GetProject()->ApplyTextVars( pdfJob->GetVarOverrides() );
  865. brd->SynchronizeProperties();
  866. if( pdfJob->m_checkZonesBeforePlot )
  867. {
  868. if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
  869. toolManager->RegisterTool( new ZONE_FILLER_TOOL );
  870. toolManager->GetTool<ZONE_FILLER_TOOL>()->CheckAllZones( nullptr );
  871. }
  872. if( pdfJob->m_argLayers )
  873. pdfJob->m_plotLayerSequence = convertLayerArg( pdfJob->m_argLayers.value(), brd );
  874. if( pdfJob->m_argCommonLayers )
  875. pdfJob->m_plotOnAllLayersSequence = convertLayerArg( pdfJob->m_argCommonLayers.value(), brd );
  876. if( pdfJob->m_pdfGenMode == JOB_EXPORT_PCB_PDF::GEN_MODE::ALL_LAYERS_ONE_FILE )
  877. plotAllLayersOneFile = true;
  878. if( pdfJob->m_plotLayerSequence.size() < 1 )
  879. {
  880. m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
  881. return CLI::EXIT_CODES::ERR_ARGS;
  882. }
  883. if( plotAllLayersOneFile && pdfJob->GetConfiguredOutputPath().IsEmpty() )
  884. {
  885. wxFileName fn = brd->GetFileName();
  886. fn.SetName( fn.GetName() );
  887. fn.SetExt( GetDefaultPlotExtension( PLOT_FORMAT::PDF ) );
  888. pdfJob->SetWorkingOutputPath( fn.GetFullName() );
  889. }
  890. PCB_PLOT_PARAMS plotOpts;
  891. PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, pdfJob, *m_reporter );
  892. // ensure this is set for this one gen mode
  893. if( plotAllLayersOneFile )
  894. plotOpts.m_PDFSingle = true;
  895. PCB_PLOTTER pcbPlotter( brd, m_reporter, plotOpts );
  896. wxString outPath = pdfJob->GetFullOutputPath( brd->GetProject() );
  897. if( !PATHS::EnsurePathExists( outPath, plotAllLayersOneFile ) )
  898. {
  899. m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
  900. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  901. }
  902. std::optional<wxString> layerName;
  903. std::optional<wxString> sheetName;
  904. std::optional<wxString> sheetPath;
  905. if( plotAllLayersOneFile )
  906. {
  907. if( pdfJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
  908. layerName = pdfJob->GetVarOverrides().at( wxT( "LAYER" ) );
  909. if( pdfJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
  910. sheetName = pdfJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
  911. if( pdfJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
  912. sheetPath = pdfJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
  913. }
  914. if( !pcbPlotter.Plot( outPath, pdfJob->m_plotLayerSequence,
  915. pdfJob->m_plotOnAllLayersSequence, false, plotAllLayersOneFile,
  916. layerName, sheetName, sheetPath ) )
  917. {
  918. return CLI::EXIT_CODES::ERR_UNKNOWN;
  919. }
  920. return CLI::EXIT_CODES::OK;
  921. }
  922. int PCBNEW_JOBS_HANDLER::JobExportPs( JOB* aJob )
  923. {
  924. JOB_EXPORT_PCB_PS* psJob = dynamic_cast<JOB_EXPORT_PCB_PS*>( aJob );
  925. if( psJob == nullptr )
  926. return CLI::EXIT_CODES::ERR_UNKNOWN;
  927. BOARD* brd = getBoard( psJob->m_filename );
  928. if( !brd )
  929. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  930. TOOL_MANAGER* toolManager = getToolManager( brd );
  931. psJob->SetTitleBlock( brd->GetTitleBlock() );
  932. loadOverrideDrawingSheet( brd, psJob->m_drawingSheet );
  933. brd->GetProject()->ApplyTextVars( psJob->GetVarOverrides() );
  934. brd->SynchronizeProperties();
  935. if( psJob->m_checkZonesBeforePlot )
  936. {
  937. if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
  938. toolManager->RegisterTool( new ZONE_FILLER_TOOL );
  939. toolManager->GetTool<ZONE_FILLER_TOOL>()->CheckAllZones( nullptr );
  940. }
  941. if( psJob->m_argLayers )
  942. psJob->m_plotLayerSequence = convertLayerArg( psJob->m_argLayers.value(), brd );
  943. if( psJob->m_argCommonLayers )
  944. psJob->m_plotOnAllLayersSequence = convertLayerArg( psJob->m_argCommonLayers.value(), brd );
  945. if( psJob->m_plotLayerSequence.size() < 1 )
  946. {
  947. m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
  948. return CLI::EXIT_CODES::ERR_ARGS;
  949. }
  950. bool isSingle = psJob->m_genMode == JOB_EXPORT_PCB_PS::GEN_MODE::SINGLE;
  951. if( isSingle )
  952. {
  953. if( psJob->GetConfiguredOutputPath().IsEmpty() )
  954. {
  955. wxFileName fn = brd->GetFileName();
  956. fn.SetName( fn.GetName() );
  957. fn.SetExt( GetDefaultPlotExtension( PLOT_FORMAT::POST ) );
  958. psJob->SetWorkingOutputPath( fn.GetFullName() );
  959. }
  960. }
  961. wxString outPath = psJob->GetFullOutputPath( brd->GetProject() );
  962. if( !PATHS::EnsurePathExists( outPath, isSingle ) )
  963. {
  964. m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
  965. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  966. }
  967. PCB_PLOT_PARAMS plotOpts;
  968. PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, psJob, *m_reporter );
  969. PCB_PLOTTER pcbPlotter( brd, m_reporter, plotOpts );
  970. std::optional<wxString> layerName;
  971. std::optional<wxString> sheetName;
  972. std::optional<wxString> sheetPath;
  973. if( isSingle )
  974. {
  975. if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
  976. layerName = psJob->GetVarOverrides().at( wxT( "LAYER" ) );
  977. if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
  978. sheetName = psJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
  979. if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
  980. sheetPath = psJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
  981. }
  982. if( !pcbPlotter.Plot( outPath, psJob->m_plotLayerSequence, psJob->m_plotOnAllLayersSequence, false, isSingle,
  983. layerName, sheetName, sheetPath ) )
  984. {
  985. return CLI::EXIT_CODES::ERR_UNKNOWN;
  986. }
  987. return CLI::EXIT_CODES::OK;
  988. }
  989. int PCBNEW_JOBS_HANDLER::JobExportGerbers( JOB* aJob )
  990. {
  991. int exitCode = CLI::EXIT_CODES::OK;
  992. JOB_EXPORT_PCB_GERBERS* aGerberJob = dynamic_cast<JOB_EXPORT_PCB_GERBERS*>( aJob );
  993. if( aGerberJob == nullptr )
  994. return CLI::EXIT_CODES::ERR_UNKNOWN;
  995. BOARD* brd = getBoard( aGerberJob->m_filename );
  996. if( !brd )
  997. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  998. TOOL_MANAGER* toolManager = getToolManager( brd );
  999. wxString outPath = aGerberJob->GetFullOutputPath( brd->GetProject() );
  1000. if( !PATHS::EnsurePathExists( outPath, false ) )
  1001. {
  1002. m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
  1003. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1004. }
  1005. aJob->SetTitleBlock( brd->GetTitleBlock() );
  1006. loadOverrideDrawingSheet( brd, aGerberJob->m_drawingSheet );
  1007. brd->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
  1008. brd->SynchronizeProperties();
  1009. if( aGerberJob->m_checkZonesBeforePlot )
  1010. {
  1011. if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
  1012. toolManager->RegisterTool( new ZONE_FILLER_TOOL );
  1013. toolManager->GetTool<ZONE_FILLER_TOOL>()->CheckAllZones( nullptr );
  1014. }
  1015. bool hasLayerListSpecified = false; // will be true if the user layer list is not empty
  1016. if( aGerberJob->m_argLayers )
  1017. {
  1018. if( !aGerberJob->m_argLayers.value().empty() )
  1019. {
  1020. aGerberJob->m_plotLayerSequence = convertLayerArg( aGerberJob->m_argLayers.value(), brd );
  1021. hasLayerListSpecified = true;
  1022. }
  1023. else
  1024. {
  1025. aGerberJob->m_plotLayerSequence = LSET::AllLayersMask().SeqStackupForPlotting();
  1026. }
  1027. }
  1028. if( aGerberJob->m_argCommonLayers )
  1029. aGerberJob->m_plotOnAllLayersSequence = convertLayerArg( aGerberJob->m_argCommonLayers.value(), brd );
  1030. PCB_PLOT_PARAMS boardPlotOptions = brd->GetPlotOptions();
  1031. GERBER_JOBFILE_WRITER jobfile_writer( brd );
  1032. wxString fileExt;
  1033. if( aGerberJob->m_useBoardPlotParams )
  1034. {
  1035. // The board plot options are saved with all copper layers enabled, even those that don't
  1036. // exist in the current stackup. This is done so the layers are automatically enabled in the plot
  1037. // dialog when the user enables them. We need to filter out these not-enabled layers here so
  1038. // we don't plot 32 layers when we only have 4, etc.
  1039. LSET plotLayers = ( boardPlotOptions.GetLayerSelection() & LSET::AllNonCuMask() )
  1040. | ( brd->GetEnabledLayers() & LSET::AllCuMask() );
  1041. aGerberJob->m_plotLayerSequence = plotLayers.SeqStackupForPlotting();
  1042. aGerberJob->m_plotOnAllLayersSequence = boardPlotOptions.GetPlotOnAllLayersSequence();
  1043. }
  1044. else
  1045. {
  1046. // default to the board enabled layers, but only if the user has not specifed a layer list
  1047. // ( m_plotLayerSequence can be empty with a broken user layer list)
  1048. if( aGerberJob->m_plotLayerSequence.empty() && !hasLayerListSpecified )
  1049. aGerberJob->m_plotLayerSequence = brd->GetEnabledLayers().SeqStackupForPlotting();
  1050. }
  1051. // Ensure layers to plot are restricted to enabled layers of the board to plot
  1052. LSET layersToPlot = LSET( { aGerberJob->m_plotLayerSequence } ) & brd->GetEnabledLayers();
  1053. for( PCB_LAYER_ID layer : layersToPlot.UIOrder() )
  1054. {
  1055. LSEQ plotSequence;
  1056. // Base layer always gets plotted first.
  1057. plotSequence.push_back( layer );
  1058. // Now all the "include on all" layers
  1059. for( PCB_LAYER_ID layer_all : aGerberJob->m_plotOnAllLayersSequence )
  1060. {
  1061. // Don't plot the same layer more than once;
  1062. if( find( plotSequence.begin(), plotSequence.end(), layer_all ) != plotSequence.end() )
  1063. continue;
  1064. plotSequence.push_back( layer_all );
  1065. }
  1066. // Pick the basename from the board file
  1067. wxFileName fn( brd->GetFileName() );
  1068. wxString layerName = brd->GetLayerName( layer );
  1069. wxString sheetName;
  1070. wxString sheetPath;
  1071. PCB_PLOT_PARAMS plotOpts;
  1072. if( aGerberJob->m_useBoardPlotParams )
  1073. plotOpts = boardPlotOptions;
  1074. else
  1075. PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aGerberJob, *m_reporter );
  1076. if( plotOpts.GetUseGerberProtelExtensions() )
  1077. fileExt = GetGerberProtelExtension( layer );
  1078. else
  1079. fileExt = FILEEXT::GerberFileExtension;
  1080. BuildPlotFileName( &fn, outPath, layerName, fileExt );
  1081. wxString fullname = fn.GetFullName();
  1082. if( m_progressReporter )
  1083. {
  1084. m_progressReporter->AdvancePhase( wxString::Format( _( "Exporting %s" ), fullname ) );
  1085. m_progressReporter->KeepRefreshing();
  1086. }
  1087. jobfile_writer.AddGbrFile( layer, fullname );
  1088. if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
  1089. layerName = aJob->GetVarOverrides().at( wxT( "LAYER" ) );
  1090. if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
  1091. sheetName = aJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
  1092. if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
  1093. sheetPath = aJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
  1094. // We are feeding it one layer at the start here to silence a logic check
  1095. GERBER_PLOTTER* plotter;
  1096. plotter = (GERBER_PLOTTER*) StartPlotBoard( brd, &plotOpts, layer, layerName,
  1097. fn.GetFullPath(), sheetName, sheetPath );
  1098. if( plotter )
  1099. {
  1100. m_reporter->Report( wxString::Format( _( "Plotted to '%s'.\n" ), fn.GetFullPath() ),
  1101. RPT_SEVERITY_ACTION );
  1102. PlotBoardLayers( brd, plotter, plotSequence, plotOpts );
  1103. plotter->EndPlot();
  1104. }
  1105. else
  1106. {
  1107. m_reporter->Report( wxString::Format( _( "Failed to plot to '%s'.\n" ), fn.GetFullPath() ),
  1108. RPT_SEVERITY_ERROR );
  1109. exitCode = CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1110. }
  1111. delete plotter;
  1112. }
  1113. if( aGerberJob->m_createJobsFile )
  1114. {
  1115. wxFileName fn( brd->GetFileName() );
  1116. // Build gerber job file from basename
  1117. BuildPlotFileName( &fn, outPath, wxT( "job" ), FILEEXT::GerberJobFileExtension );
  1118. jobfile_writer.CreateJobFile( fn.GetFullPath() );
  1119. }
  1120. return exitCode;
  1121. }
  1122. int PCBNEW_JOBS_HANDLER::JobExportGencad( JOB* aJob )
  1123. {
  1124. JOB_EXPORT_PCB_GENCAD* aGencadJob = dynamic_cast<JOB_EXPORT_PCB_GENCAD*>( aJob );
  1125. if( aGencadJob == nullptr )
  1126. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1127. BOARD* brd = LoadBoard( aGencadJob->m_filename, true ); // Ensure m_board is of type BOARD*
  1128. if( brd == nullptr )
  1129. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1130. GENCAD_EXPORTER exporter( brd );
  1131. VECTOR2I GencadOffset;
  1132. VECTOR2I auxOrigin = brd->GetDesignSettings().GetAuxOrigin();
  1133. GencadOffset.x = aGencadJob->m_useDrillOrigin ? auxOrigin.x : 0;
  1134. GencadOffset.y = aGencadJob->m_useDrillOrigin ? auxOrigin.y : 0;
  1135. exporter.FlipBottomPads( aGencadJob->m_flipBottomPads );
  1136. exporter.UsePinNamesUnique( aGencadJob->m_useUniquePins );
  1137. exporter.UseIndividualShapes( aGencadJob->m_useIndividualShapes );
  1138. exporter.SetPlotOffet( GencadOffset );
  1139. exporter.StoreOriginCoordsInFile( aGencadJob->m_storeOriginCoords );
  1140. if( aGencadJob->GetConfiguredOutputPath().IsEmpty() )
  1141. {
  1142. wxFileName fn = brd->GetFileName();
  1143. fn.SetName( fn.GetName() );
  1144. fn.SetExt( FILEEXT::GencadFileExtension );
  1145. aGencadJob->SetWorkingOutputPath( fn.GetFullName() );
  1146. }
  1147. wxString outPath = aGencadJob->GetFullOutputPath( brd->GetProject() );
  1148. if( !PATHS::EnsurePathExists( outPath, true ) )
  1149. {
  1150. m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
  1151. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1152. }
  1153. if( !exporter.WriteFile( outPath ) )
  1154. {
  1155. m_reporter->Report( wxString::Format( _( "Failed to create file '%s'.\n" ), outPath ),
  1156. RPT_SEVERITY_ERROR );
  1157. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1158. }
  1159. m_reporter->Report( _( "Successfully created genCAD file\n" ), RPT_SEVERITY_INFO );
  1160. return CLI::EXIT_CODES::OK;
  1161. }
  1162. int PCBNEW_JOBS_HANDLER::JobExportGerber( JOB* aJob )
  1163. {
  1164. int exitCode = CLI::EXIT_CODES::OK;
  1165. JOB_EXPORT_PCB_GERBER* aGerberJob = dynamic_cast<JOB_EXPORT_PCB_GERBER*>( aJob );
  1166. if( aGerberJob == nullptr )
  1167. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1168. BOARD* brd = getBoard( aGerberJob->m_filename );
  1169. if( !brd )
  1170. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  1171. TOOL_MANAGER* toolManager = getToolManager( brd );
  1172. aJob->SetTitleBlock( brd->GetTitleBlock() );
  1173. brd->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
  1174. brd->SynchronizeProperties();
  1175. if( aGerberJob->m_checkZonesBeforePlot )
  1176. {
  1177. if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
  1178. toolManager->RegisterTool( new ZONE_FILLER_TOOL );
  1179. toolManager->GetTool<ZONE_FILLER_TOOL>()->CheckAllZones( nullptr );
  1180. }
  1181. if( aGerberJob->m_argLayers )
  1182. aGerberJob->m_plotLayerSequence = convertLayerArg( aGerberJob->m_argLayers.value(), brd );
  1183. if( aGerberJob->m_argCommonLayers )
  1184. aGerberJob->m_plotOnAllLayersSequence = convertLayerArg( aGerberJob->m_argCommonLayers.value(), brd );
  1185. if( aGerberJob->m_plotLayerSequence.size() < 1 )
  1186. {
  1187. m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
  1188. return CLI::EXIT_CODES::ERR_ARGS;
  1189. }
  1190. if( aGerberJob->GetConfiguredOutputPath().IsEmpty() )
  1191. {
  1192. wxFileName fn = brd->GetFileName();
  1193. fn.SetName( fn.GetName() );
  1194. fn.SetExt( GetDefaultPlotExtension( PLOT_FORMAT::GERBER ) );
  1195. aGerberJob->SetWorkingOutputPath( fn.GetFullName() );
  1196. }
  1197. PCB_PLOT_PARAMS plotOpts;
  1198. PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aGerberJob, *m_reporter );
  1199. plotOpts.SetLayerSelection( aGerberJob->m_plotLayerSequence );
  1200. plotOpts.SetPlotOnAllLayersSequence( aGerberJob->m_plotOnAllLayersSequence );
  1201. PCB_LAYER_ID layer = UNDEFINED_LAYER;
  1202. wxString layerName;
  1203. wxString sheetName;
  1204. wxString sheetPath;
  1205. wxString outPath = aGerberJob->GetFullOutputPath( brd->GetProject() );
  1206. // The first layer will be treated as the layer name for the gerber header,
  1207. // the other layers will be treated equivalent to the "Plot on All Layers" option
  1208. // in the GUI
  1209. if( aGerberJob->m_plotLayerSequence.size() >= 1 )
  1210. {
  1211. layer = aGerberJob->m_plotLayerSequence.front();
  1212. layerName = brd->GetLayerName( layer );
  1213. }
  1214. if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
  1215. layerName = aJob->GetVarOverrides().at( wxT( "LAYER" ) );
  1216. if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
  1217. sheetName = aJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
  1218. if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
  1219. sheetPath = aJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
  1220. // We are feeding it one layer at the start here to silence a logic check
  1221. PLOTTER* plotter = StartPlotBoard( brd, &plotOpts, layer, layerName, outPath, sheetName,
  1222. sheetPath );
  1223. if( plotter )
  1224. {
  1225. PlotBoardLayers( brd, plotter, aGerberJob->m_plotLayerSequence, plotOpts );
  1226. plotter->EndPlot();
  1227. }
  1228. else
  1229. {
  1230. m_reporter->Report( wxString::Format( _( "Failed to plot to '%s'.\n" ), outPath ),
  1231. RPT_SEVERITY_ERROR );
  1232. exitCode = CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1233. }
  1234. delete plotter;
  1235. return exitCode;
  1236. }
  1237. static DRILL_PRECISION precisionListForInches( 2, 4 );
  1238. static DRILL_PRECISION precisionListForMetric( 3, 3 );
  1239. int PCBNEW_JOBS_HANDLER::JobExportDrill( JOB* aJob )
  1240. {
  1241. JOB_EXPORT_PCB_DRILL* aDrillJob = dynamic_cast<JOB_EXPORT_PCB_DRILL*>( aJob );
  1242. if( aDrillJob == nullptr )
  1243. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1244. BOARD* brd = getBoard( aDrillJob->m_filename );
  1245. if( !brd )
  1246. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  1247. aJob->SetTitleBlock( brd->GetTitleBlock() );
  1248. wxString outPath = aDrillJob->GetFullOutputPath( brd->GetProject() );
  1249. if( !PATHS::EnsurePathExists( outPath ) )
  1250. {
  1251. m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
  1252. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1253. }
  1254. std::unique_ptr<GENDRILL_WRITER_BASE> drillWriter;
  1255. if( aDrillJob->m_format == JOB_EXPORT_PCB_DRILL::DRILL_FORMAT::EXCELLON )
  1256. drillWriter = std::make_unique<EXCELLON_WRITER>( brd );
  1257. else
  1258. drillWriter = std::make_unique<GERBER_WRITER>( brd );
  1259. VECTOR2I offset;
  1260. if( aDrillJob->m_drillOrigin == JOB_EXPORT_PCB_DRILL::DRILL_ORIGIN::ABS )
  1261. offset = VECTOR2I( 0, 0 );
  1262. else
  1263. offset = brd->GetDesignSettings().GetAuxOrigin();
  1264. PLOT_FORMAT mapFormat = PLOT_FORMAT::PDF;
  1265. switch( aDrillJob->m_mapFormat )
  1266. {
  1267. case JOB_EXPORT_PCB_DRILL::MAP_FORMAT::POSTSCRIPT: mapFormat = PLOT_FORMAT::POST; break;
  1268. case JOB_EXPORT_PCB_DRILL::MAP_FORMAT::GERBER_X2: mapFormat = PLOT_FORMAT::GERBER; break;
  1269. case JOB_EXPORT_PCB_DRILL::MAP_FORMAT::DXF: mapFormat = PLOT_FORMAT::DXF; break;
  1270. case JOB_EXPORT_PCB_DRILL::MAP_FORMAT::SVG: mapFormat = PLOT_FORMAT::SVG; break;
  1271. default:
  1272. case JOB_EXPORT_PCB_DRILL::MAP_FORMAT::PDF: mapFormat = PLOT_FORMAT::PDF; break;
  1273. }
  1274. if( aDrillJob->m_format == JOB_EXPORT_PCB_DRILL::DRILL_FORMAT::EXCELLON )
  1275. {
  1276. EXCELLON_WRITER::ZEROS_FMT zeroFmt;
  1277. switch( aDrillJob->m_zeroFormat )
  1278. {
  1279. case JOB_EXPORT_PCB_DRILL::ZEROS_FORMAT::KEEP_ZEROS:
  1280. zeroFmt = EXCELLON_WRITER::KEEP_ZEROS;
  1281. break;
  1282. case JOB_EXPORT_PCB_DRILL::ZEROS_FORMAT::SUPPRESS_LEADING:
  1283. zeroFmt = EXCELLON_WRITER::SUPPRESS_LEADING;
  1284. break;
  1285. case JOB_EXPORT_PCB_DRILL::ZEROS_FORMAT::SUPPRESS_TRAILING:
  1286. zeroFmt = EXCELLON_WRITER::SUPPRESS_TRAILING;
  1287. break;
  1288. case JOB_EXPORT_PCB_DRILL::ZEROS_FORMAT::DECIMAL:
  1289. default:
  1290. zeroFmt = EXCELLON_WRITER::DECIMAL_FORMAT;
  1291. break;
  1292. }
  1293. DRILL_PRECISION precision;
  1294. if( aDrillJob->m_drillUnits == JOB_EXPORT_PCB_DRILL::DRILL_UNITS::INCH )
  1295. precision = precisionListForInches;
  1296. else
  1297. precision = precisionListForMetric;
  1298. EXCELLON_WRITER* excellonWriter = dynamic_cast<EXCELLON_WRITER*>( drillWriter.get() );
  1299. if( excellonWriter == nullptr )
  1300. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1301. excellonWriter->SetFormat( aDrillJob->m_drillUnits == JOB_EXPORT_PCB_DRILL::DRILL_UNITS::MM,
  1302. zeroFmt, precision.m_Lhs, precision.m_Rhs );
  1303. excellonWriter->SetOptions( aDrillJob->m_excellonMirrorY,
  1304. aDrillJob->m_excellonMinimalHeader,
  1305. offset, aDrillJob->m_excellonCombinePTHNPTH );
  1306. excellonWriter->SetRouteModeForOvalHoles( aDrillJob->m_excellonOvalDrillRoute );
  1307. excellonWriter->SetMapFileFormat( mapFormat );
  1308. if( !excellonWriter->CreateDrillandMapFilesSet( outPath, true, aDrillJob->m_generateMap,
  1309. m_reporter ) )
  1310. {
  1311. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1312. }
  1313. }
  1314. else if( aDrillJob->m_format == JOB_EXPORT_PCB_DRILL::DRILL_FORMAT::GERBER )
  1315. {
  1316. GERBER_WRITER* gerberWriter = dynamic_cast<GERBER_WRITER*>( drillWriter.get() );
  1317. if( gerberWriter == nullptr )
  1318. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1319. // Set gerber precision: only 5 or 6 digits for mantissa are allowed
  1320. // (SetFormat() accept 5 or 6, and any other value set the precision to 5)
  1321. // the integer part precision is always 4, and units always mm
  1322. gerberWriter->SetFormat( aDrillJob->m_gerberPrecision );
  1323. gerberWriter->SetOptions( offset );
  1324. gerberWriter->SetMapFileFormat( mapFormat );
  1325. if( !gerberWriter->CreateDrillandMapFilesSet( outPath, true, aDrillJob->m_generateMap,
  1326. aDrillJob->m_generateTenting, m_reporter ) )
  1327. {
  1328. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1329. }
  1330. }
  1331. return CLI::EXIT_CODES::OK;
  1332. }
  1333. int PCBNEW_JOBS_HANDLER::JobExportPos( JOB* aJob )
  1334. {
  1335. JOB_EXPORT_PCB_POS* aPosJob = dynamic_cast<JOB_EXPORT_PCB_POS*>( aJob );
  1336. if( aPosJob == nullptr )
  1337. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1338. BOARD* brd = getBoard( aPosJob->m_filename );
  1339. if( !brd )
  1340. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  1341. aJob->SetTitleBlock( brd->GetTitleBlock() );
  1342. if( aPosJob->GetConfiguredOutputPath().IsEmpty() )
  1343. {
  1344. wxFileName fn = brd->GetFileName();
  1345. fn.SetName( fn.GetName() );
  1346. if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::ASCII )
  1347. fn.SetExt( FILEEXT::FootprintPlaceFileExtension );
  1348. else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV )
  1349. fn.SetExt( FILEEXT::CsvFileExtension );
  1350. else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::GERBER )
  1351. fn.SetExt( FILEEXT::GerberFileExtension );
  1352. aPosJob->SetWorkingOutputPath( fn.GetFullName() );
  1353. }
  1354. wxString outPath = aPosJob->GetFullOutputPath( brd->GetProject() );
  1355. if( !PATHS::EnsurePathExists( outPath, true ) )
  1356. {
  1357. m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
  1358. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1359. }
  1360. if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::ASCII
  1361. || aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV )
  1362. {
  1363. wxFileName fn( outPath );
  1364. wxString baseName = fn.GetName();
  1365. auto exportPlaceFile =
  1366. [&]( bool frontSide, bool backSide, const wxString& curr_outPath ) -> bool
  1367. {
  1368. FILE* file = wxFopen( curr_outPath, wxS( "wt" ) );
  1369. wxCHECK( file, false );
  1370. PLACE_FILE_EXPORTER exporter( brd,
  1371. aPosJob->m_units == JOB_EXPORT_PCB_POS::UNITS::MM,
  1372. aPosJob->m_smdOnly,
  1373. aPosJob->m_excludeFootprintsWithTh,
  1374. aPosJob->m_excludeDNP,
  1375. frontSide,
  1376. backSide,
  1377. aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV,
  1378. aPosJob->m_useDrillPlaceFileOrigin,
  1379. aPosJob->m_negateBottomX );
  1380. std::string data = exporter.GenPositionData();
  1381. fputs( data.c_str(), file );
  1382. fclose( file );
  1383. return true;
  1384. };
  1385. if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH && !aPosJob->m_singleFile )
  1386. {
  1387. fn.SetName( PLACE_FILE_EXPORTER::DecorateFilename( baseName, true, false ) );
  1388. if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV && !aPosJob->m_nakedFilename )
  1389. fn.SetName( fn.GetName() + wxT( "-" ) + FILEEXT::FootprintPlaceFileExtension );
  1390. if( exportPlaceFile( true, false, fn.GetFullPath() ) )
  1391. {
  1392. m_reporter->Report( wxString::Format( _( "Wrote front position data to '%s'.\n" ),
  1393. fn.GetFullPath() ),
  1394. RPT_SEVERITY_ACTION );
  1395. aPosJob->AddOutput( fn.GetFullPath() );
  1396. }
  1397. else
  1398. {
  1399. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1400. }
  1401. fn.SetName( PLACE_FILE_EXPORTER::DecorateFilename( baseName, false, true ) );
  1402. if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV && !aPosJob->m_nakedFilename )
  1403. fn.SetName( fn.GetName() + wxT( "-" ) + FILEEXT::FootprintPlaceFileExtension );
  1404. if( exportPlaceFile( false, true, fn.GetFullPath() ) )
  1405. {
  1406. m_reporter->Report( wxString::Format( _( "Wrote back position data to '%s'.\n" ),
  1407. fn.GetFullPath() ),
  1408. RPT_SEVERITY_ACTION );
  1409. aPosJob->AddOutput( fn.GetFullPath() );
  1410. }
  1411. else
  1412. {
  1413. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1414. }
  1415. }
  1416. else
  1417. {
  1418. bool front = aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::FRONT
  1419. || aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH;
  1420. bool back = aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BACK
  1421. || aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH;
  1422. if( !aPosJob->m_nakedFilename )
  1423. {
  1424. fn.SetName( PLACE_FILE_EXPORTER::DecorateFilename( fn.GetName(), front, back ) );
  1425. if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV )
  1426. fn.SetName( fn.GetName() + wxT( "-" ) + FILEEXT::FootprintPlaceFileExtension );
  1427. }
  1428. if( exportPlaceFile( front, back, fn.GetFullPath() ) )
  1429. {
  1430. m_reporter->Report( wxString::Format( _( "Wrote position data to '%s'.\n" ),
  1431. fn.GetFullPath() ),
  1432. RPT_SEVERITY_ACTION );
  1433. aPosJob->AddOutput( fn.GetFullPath() );
  1434. }
  1435. else
  1436. {
  1437. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1438. }
  1439. }
  1440. }
  1441. else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::GERBER )
  1442. {
  1443. PLACEFILE_GERBER_WRITER exporter( brd );
  1444. PCB_LAYER_ID gbrLayer = F_Cu;
  1445. wxString outPath_base = outPath;
  1446. if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::FRONT
  1447. || aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH )
  1448. {
  1449. if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH || !aPosJob->m_nakedFilename )
  1450. outPath = exporter.GetPlaceFileName( outPath, gbrLayer );
  1451. if( exporter.CreatePlaceFile( outPath, gbrLayer, aPosJob->m_gerberBoardEdge, aPosJob->m_excludeDNP ) >= 0 )
  1452. {
  1453. m_reporter->Report( wxString::Format( _( "Wrote front position data to '%s'.\n" ), outPath ),
  1454. RPT_SEVERITY_ACTION );
  1455. aPosJob->AddOutput( outPath );
  1456. }
  1457. else
  1458. {
  1459. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1460. }
  1461. }
  1462. if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BACK
  1463. || aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH )
  1464. {
  1465. gbrLayer = B_Cu;
  1466. outPath = outPath_base;
  1467. if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH || !aPosJob->m_nakedFilename )
  1468. outPath = exporter.GetPlaceFileName( outPath, gbrLayer );
  1469. if( exporter.CreatePlaceFile( outPath, gbrLayer, aPosJob->m_gerberBoardEdge, aPosJob->m_excludeDNP ) >= 0 )
  1470. {
  1471. m_reporter->Report( wxString::Format( _( "Wrote back position data to '%s'.\n" ), outPath ),
  1472. RPT_SEVERITY_ACTION );
  1473. aPosJob->AddOutput( outPath );
  1474. }
  1475. else
  1476. {
  1477. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1478. }
  1479. }
  1480. }
  1481. return CLI::EXIT_CODES::OK;
  1482. }
  1483. int PCBNEW_JOBS_HANDLER::JobExportFpUpgrade( JOB* aJob )
  1484. {
  1485. JOB_FP_UPGRADE* upgradeJob = dynamic_cast<JOB_FP_UPGRADE*>( aJob );
  1486. if( upgradeJob == nullptr )
  1487. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1488. PCB_IO_MGR::PCB_FILE_T fileType = PCB_IO_MGR::GuessPluginTypeFromLibPath( upgradeJob->m_libraryPath );
  1489. if( !upgradeJob->m_outputLibraryPath.IsEmpty() )
  1490. {
  1491. if( wxFile::Exists( upgradeJob->m_outputLibraryPath )
  1492. || wxDir::Exists( upgradeJob->m_outputLibraryPath) )
  1493. {
  1494. m_reporter->Report( _( "Output path must not conflict with existing path\n" ),
  1495. RPT_SEVERITY_ERROR );
  1496. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1497. }
  1498. }
  1499. else if( fileType != PCB_IO_MGR::KICAD_SEXP )
  1500. {
  1501. m_reporter->Report( _( "Output path must be specified to convert legacy and non-KiCad libraries\n" ),
  1502. RPT_SEVERITY_ERROR );
  1503. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1504. }
  1505. if( fileType == PCB_IO_MGR::KICAD_SEXP )
  1506. {
  1507. if( !wxDir::Exists( upgradeJob->m_libraryPath ) )
  1508. {
  1509. m_reporter->Report( _( "Footprint library path does not exist or is not accessible\n" ),
  1510. RPT_SEVERITY_ERROR );
  1511. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  1512. }
  1513. PCB_IO_KICAD_SEXPR pcb_io( CTL_FOR_LIBRARY );
  1514. FP_CACHE fpLib( &pcb_io, upgradeJob->m_libraryPath );
  1515. try
  1516. {
  1517. fpLib.Load();
  1518. }
  1519. catch( ... )
  1520. {
  1521. m_reporter->Report( _( "Unable to load library\n" ), RPT_SEVERITY_ERROR );
  1522. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1523. }
  1524. if( m_progressReporter )
  1525. m_progressReporter->KeepRefreshing();
  1526. bool shouldSave = upgradeJob->m_force;
  1527. for( const auto& footprint : fpLib.GetFootprints() )
  1528. {
  1529. if( footprint.second->GetFootprint()->GetFileFormatVersionAtLoad() < SEXPR_BOARD_FILE_VERSION )
  1530. shouldSave = true;
  1531. }
  1532. if( shouldSave )
  1533. {
  1534. try
  1535. {
  1536. if( !upgradeJob->m_outputLibraryPath.IsEmpty() )
  1537. fpLib.SetPath( upgradeJob->m_outputLibraryPath );
  1538. fpLib.Save();
  1539. }
  1540. catch( ... )
  1541. {
  1542. m_reporter->Report( _( "Unable to save library\n" ), RPT_SEVERITY_ERROR );
  1543. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1544. }
  1545. }
  1546. else
  1547. {
  1548. m_reporter->Report( _( "Footprint library was not updated\n" ), RPT_SEVERITY_ERROR );
  1549. }
  1550. }
  1551. else
  1552. {
  1553. if( !PCB_IO_MGR::ConvertLibrary( nullptr, upgradeJob->m_libraryPath,
  1554. upgradeJob->m_outputLibraryPath, nullptr /* REPORTER */ ) )
  1555. {
  1556. m_reporter->Report( ( "Unable to convert library\n" ), RPT_SEVERITY_ERROR );
  1557. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1558. }
  1559. }
  1560. return CLI::EXIT_CODES::OK;
  1561. }
  1562. int PCBNEW_JOBS_HANDLER::JobExportFpSvg( JOB* aJob )
  1563. {
  1564. JOB_FP_EXPORT_SVG* svgJob = dynamic_cast<JOB_FP_EXPORT_SVG*>( aJob );
  1565. if( svgJob == nullptr )
  1566. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1567. PCB_IO_KICAD_SEXPR pcb_io( CTL_FOR_LIBRARY );
  1568. FP_CACHE fpLib( &pcb_io, svgJob->m_libraryPath );
  1569. if( svgJob->m_argLayers )
  1570. {
  1571. if( !svgJob->m_argLayers.value().empty() )
  1572. svgJob->m_plotLayerSequence = convertLayerArg( svgJob->m_argLayers.value(), nullptr );
  1573. else
  1574. svgJob->m_plotLayerSequence = LSET::AllLayersMask().SeqStackupForPlotting();
  1575. }
  1576. try
  1577. {
  1578. fpLib.Load();
  1579. }
  1580. catch( ... )
  1581. {
  1582. m_reporter->Report( _( "Unable to load library\n" ), RPT_SEVERITY_ERROR );
  1583. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1584. }
  1585. wxString outPath = svgJob->GetFullOutputPath( nullptr );
  1586. if( !PATHS::EnsurePathExists( outPath, true ) )
  1587. {
  1588. m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
  1589. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1590. }
  1591. int exitCode = CLI::EXIT_CODES::OK;
  1592. bool singleFpPlotted = false;
  1593. for( const auto& [fpName, fpCacheEntry] : fpLib.GetFootprints() )
  1594. {
  1595. if( m_progressReporter )
  1596. {
  1597. m_progressReporter->AdvancePhase( wxString::Format( _( "Exporting %s" ), fpName ) );
  1598. m_progressReporter->KeepRefreshing();
  1599. }
  1600. if( !svgJob->m_footprint.IsEmpty() )
  1601. {
  1602. // skip until we find the right footprint
  1603. if( fpName != svgJob->m_footprint )
  1604. continue;
  1605. else
  1606. singleFpPlotted = true;
  1607. }
  1608. exitCode = doFpExportSvg( svgJob, fpCacheEntry->GetFootprint().get() );
  1609. if( exitCode != CLI::EXIT_CODES::OK )
  1610. break;
  1611. }
  1612. if( !svgJob->m_footprint.IsEmpty() && !singleFpPlotted )
  1613. {
  1614. m_reporter->Report( _( "The given footprint could not be found to export." ) + wxS( "\n" ),
  1615. RPT_SEVERITY_ERROR );
  1616. }
  1617. return CLI::EXIT_CODES::OK;
  1618. }
  1619. int PCBNEW_JOBS_HANDLER::doFpExportSvg( JOB_FP_EXPORT_SVG* aSvgJob, const FOOTPRINT* aFootprint )
  1620. {
  1621. // the hack for now is we create fake boards containing the footprint and plot the board
  1622. // until we refactor better plot api later
  1623. std::unique_ptr<BOARD> brd;
  1624. brd.reset( CreateEmptyBoard() );
  1625. brd->GetProject()->ApplyTextVars( aSvgJob->GetVarOverrides() );
  1626. brd->SynchronizeProperties();
  1627. FOOTPRINT* fp = dynamic_cast<FOOTPRINT*>( aFootprint->Clone() );
  1628. if( fp == nullptr )
  1629. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1630. fp->SetLink( niluuid );
  1631. fp->SetFlags( IS_NEW );
  1632. fp->SetParent( brd.get() );
  1633. for( PAD* pad : fp->Pads() )
  1634. {
  1635. pad->SetLocalRatsnestVisible( false );
  1636. pad->SetNetCode( 0 );
  1637. }
  1638. fp->SetOrientation( ANGLE_0 );
  1639. fp->SetPosition( VECTOR2I( 0, 0 ) );
  1640. brd->Add( fp, ADD_MODE::INSERT, true );
  1641. wxFileName outputFile;
  1642. outputFile.SetPath( aSvgJob->GetFullOutputPath(nullptr) );
  1643. outputFile.SetName( aFootprint->GetFPID().GetLibItemName().wx_str() );
  1644. outputFile.SetExt( FILEEXT::SVGFileExtension );
  1645. m_reporter->Report( wxString::Format( _( "Plotting footprint '%s' to '%s'\n" ),
  1646. aFootprint->GetFPID().GetLibItemName().wx_str(),
  1647. outputFile.GetFullPath() ),
  1648. RPT_SEVERITY_ACTION );
  1649. PCB_PLOT_PARAMS plotOpts;
  1650. PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aSvgJob, *m_reporter );
  1651. // always fixed for the svg plot
  1652. plotOpts.SetPlotFrameRef( false );
  1653. plotOpts.SetSvgFitPageToBoard( true );
  1654. plotOpts.SetMirror( false );
  1655. plotOpts.SetSkipPlotNPTH_Pads( false );
  1656. if( plotOpts.GetSketchPadsOnFabLayers() )
  1657. {
  1658. plotOpts.SetPlotPadNumbers( true );
  1659. }
  1660. PCB_PLOTTER plotter( brd.get(), m_reporter, plotOpts );
  1661. if( !plotter.Plot( outputFile.GetFullPath(),
  1662. aSvgJob->m_plotLayerSequence,
  1663. aSvgJob->m_plotOnAllLayersSequence,
  1664. false,
  1665. true,
  1666. wxEmptyString, wxEmptyString,
  1667. wxEmptyString ) )
  1668. {
  1669. m_reporter->Report( _( "Error creating svg file" ) + wxS( "\n" ), RPT_SEVERITY_ERROR );
  1670. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1671. }
  1672. return CLI::EXIT_CODES::OK;
  1673. }
  1674. int PCBNEW_JOBS_HANDLER::JobExportDrc( JOB* aJob )
  1675. {
  1676. JOB_PCB_DRC* drcJob = dynamic_cast<JOB_PCB_DRC*>( aJob );
  1677. if( drcJob == nullptr )
  1678. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1679. BOARD* brd = getBoard( drcJob->m_filename );
  1680. if( !brd )
  1681. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  1682. aJob->SetTitleBlock( brd->GetTitleBlock() );
  1683. brd->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
  1684. brd->SynchronizeProperties();
  1685. if( drcJob->GetConfiguredOutputPath().IsEmpty() )
  1686. {
  1687. wxFileName fn = brd->GetFileName();
  1688. fn.SetName( fn.GetName() + wxS( "-drc" ) );
  1689. if( drcJob->m_format == JOB_PCB_DRC::OUTPUT_FORMAT::JSON )
  1690. fn.SetExt( FILEEXT::JsonFileExtension );
  1691. else
  1692. fn.SetExt( FILEEXT::ReportFileExtension );
  1693. drcJob->SetWorkingOutputPath( fn.GetFullName() );
  1694. }
  1695. wxString outPath = drcJob->GetFullOutputPath( brd->GetProject() );
  1696. if( !PATHS::EnsurePathExists( outPath, true ) )
  1697. {
  1698. m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
  1699. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1700. }
  1701. EDA_UNITS units;
  1702. switch( drcJob->m_units )
  1703. {
  1704. case JOB_PCB_DRC::UNITS::INCH: units = EDA_UNITS::INCH; break;
  1705. case JOB_PCB_DRC::UNITS::MILS: units = EDA_UNITS::MILS; break;
  1706. case JOB_PCB_DRC::UNITS::MM: units = EDA_UNITS::MM; break;
  1707. default: units = EDA_UNITS::MM; break;
  1708. }
  1709. std::shared_ptr<DRC_ENGINE> drcEngine = brd->GetDesignSettings().m_DRCEngine;
  1710. std::unique_ptr<NETLIST> netlist = std::make_unique<NETLIST>();
  1711. drcEngine->SetDrawingSheet( getDrawingSheetProxyView( brd ) );
  1712. // BOARD_COMMIT uses TOOL_MANAGER to grab the board internally so we must give it one
  1713. TOOL_MANAGER* toolManager = getToolManager( brd );
  1714. BOARD_COMMIT commit( toolManager );
  1715. bool checkParity = drcJob->m_parity;
  1716. std::string netlist_str;
  1717. if( checkParity )
  1718. {
  1719. wxString annotateMsg = _( "Schematic parity tests require a fully annotated schematic." );
  1720. netlist_str = annotateMsg;
  1721. // The KIFACE_NETLIST_SCHEMATIC function has some broken-ness that the schematic
  1722. // frame's version does not, but it is the only one that works in CLI, so we use it
  1723. // if we don't have the sch frame open.
  1724. // TODO: clean this up, see https://gitlab.com/kicad/code/kicad/-/issues/19929
  1725. if( m_kiway->Player( FRAME_SCH, false ) )
  1726. {
  1727. m_kiway->ExpressMail( FRAME_SCH, MAIL_SCH_GET_NETLIST, netlist_str );
  1728. }
  1729. else
  1730. {
  1731. wxFileName schematicPath( drcJob->m_filename );
  1732. schematicPath.SetExt( FILEEXT::KiCadSchematicFileExtension );
  1733. if( !schematicPath.Exists() )
  1734. schematicPath.SetExt( FILEEXT::LegacySchematicFileExtension );
  1735. if( !schematicPath.Exists() )
  1736. {
  1737. m_reporter->Report( _( "Failed to fetch schematic netlist for parity tests.\n" ),
  1738. RPT_SEVERITY_ERROR );
  1739. checkParity = false;
  1740. }
  1741. else
  1742. {
  1743. typedef bool ( *NETLIST_FN_PTR )( const wxString&, std::string& );
  1744. KIFACE* eeschema = m_kiway->KiFACE( KIWAY::FACE_SCH );
  1745. NETLIST_FN_PTR netlister =
  1746. (NETLIST_FN_PTR) eeschema->IfaceOrAddress( KIFACE_NETLIST_SCHEMATIC );
  1747. ( *netlister )( schematicPath.GetFullPath(), netlist_str );
  1748. }
  1749. }
  1750. if( netlist_str == annotateMsg )
  1751. {
  1752. m_reporter->Report( wxString( netlist_str ) + wxT( "\n" ), RPT_SEVERITY_ERROR );
  1753. checkParity = false;
  1754. }
  1755. }
  1756. if( checkParity )
  1757. {
  1758. try
  1759. {
  1760. STRING_LINE_READER* lineReader = new STRING_LINE_READER( netlist_str,
  1761. _( "Eeschema netlist" ) );
  1762. KICAD_NETLIST_READER netlistReader( lineReader, netlist.get() );
  1763. netlistReader.LoadNetlist();
  1764. }
  1765. catch( const IO_ERROR& )
  1766. {
  1767. m_reporter->Report( _( "Failed to fetch schematic netlist for parity tests.\n" ),
  1768. RPT_SEVERITY_ERROR );
  1769. checkParity = false;
  1770. }
  1771. drcEngine->SetSchematicNetlist( netlist.get() );
  1772. }
  1773. if( drcJob->m_refillZones )
  1774. {
  1775. if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
  1776. toolManager->RegisterTool( new ZONE_FILLER_TOOL );
  1777. toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
  1778. }
  1779. drcEngine->SetProgressReporter( m_progressReporter );
  1780. drcEngine->SetViolationHandler(
  1781. [&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos, int aLayer,
  1782. const std::vector<PCB_SHAPE>& aShapes )
  1783. {
  1784. PCB_MARKER* marker = new PCB_MARKER( aItem, aPos, aLayer );
  1785. marker->SetShapes( aShapes );
  1786. commit.Add( marker );
  1787. } );
  1788. brd->RecordDRCExclusions();
  1789. brd->DeleteMARKERs( true, true );
  1790. drcEngine->RunTests( units, drcJob->m_reportAllTrackErrors, checkParity );
  1791. drcEngine->ClearViolationHandler();
  1792. commit.Push( _( "DRC" ), SKIP_UNDO | SKIP_SET_DIRTY );
  1793. // Update the exclusion status on any excluded markers that still exist.
  1794. brd->ResolveDRCExclusions( false );
  1795. std::shared_ptr<DRC_ITEMS_PROVIDER> markersProvider = std::make_shared<DRC_ITEMS_PROVIDER>(
  1796. brd, MARKER_BASE::MARKER_DRC, MARKER_BASE::MARKER_DRAWING_SHEET );
  1797. std::shared_ptr<DRC_ITEMS_PROVIDER> ratsnestProvider =
  1798. std::make_shared<DRC_ITEMS_PROVIDER>( brd, MARKER_BASE::MARKER_RATSNEST );
  1799. std::shared_ptr<DRC_ITEMS_PROVIDER> fpWarningsProvider =
  1800. std::make_shared<DRC_ITEMS_PROVIDER>( brd, MARKER_BASE::MARKER_PARITY );
  1801. markersProvider->SetSeverities( drcJob->m_severity );
  1802. ratsnestProvider->SetSeverities( drcJob->m_severity );
  1803. fpWarningsProvider->SetSeverities( drcJob->m_severity );
  1804. m_reporter->Report( wxString::Format( _( "Found %d violations\n" ),
  1805. markersProvider->GetCount() ),
  1806. RPT_SEVERITY_INFO );
  1807. m_reporter->Report( wxString::Format( _( "Found %d unconnected items\n" ),
  1808. ratsnestProvider->GetCount() ),
  1809. RPT_SEVERITY_INFO );
  1810. if( checkParity )
  1811. {
  1812. m_reporter->Report( wxString::Format( _( "Found %d schematic parity issues\n" ),
  1813. fpWarningsProvider->GetCount() ),
  1814. RPT_SEVERITY_INFO );
  1815. }
  1816. DRC_REPORT reportWriter( brd, units, markersProvider, ratsnestProvider, fpWarningsProvider );
  1817. bool wroteReport = false;
  1818. if( drcJob->m_format == JOB_PCB_DRC::OUTPUT_FORMAT::JSON )
  1819. wroteReport = reportWriter.WriteJsonReport( outPath );
  1820. else
  1821. wroteReport = reportWriter.WriteTextReport( outPath );
  1822. if( !wroteReport )
  1823. {
  1824. m_reporter->Report( wxString::Format( _( "Unable to save DRC report to %s\n" ), outPath ),
  1825. RPT_SEVERITY_ERROR );
  1826. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1827. }
  1828. m_reporter->Report( wxString::Format( _( "Saved DRC Report to %s\n" ), outPath ),
  1829. RPT_SEVERITY_ACTION );
  1830. if( drcJob->m_refillZones && drcJob->m_saveBoard )
  1831. {
  1832. if( SaveBoard( drcJob->m_filename, brd, true ) )
  1833. {
  1834. m_reporter->Report( _( "Saved board\n" ), RPT_SEVERITY_ACTION );
  1835. }
  1836. else
  1837. {
  1838. m_reporter->Report( _( "Failed to save board.\n" ), RPT_SEVERITY_ERROR );
  1839. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1840. }
  1841. }
  1842. if( drcJob->m_exitCodeViolations )
  1843. {
  1844. if( markersProvider->GetCount() > 0 || ratsnestProvider->GetCount() > 0
  1845. || fpWarningsProvider->GetCount() > 0 )
  1846. {
  1847. return CLI::EXIT_CODES::ERR_RC_VIOLATIONS;
  1848. }
  1849. }
  1850. return CLI::EXIT_CODES::SUCCESS;
  1851. }
  1852. int PCBNEW_JOBS_HANDLER::JobExportIpc2581( JOB* aJob )
  1853. {
  1854. JOB_EXPORT_PCB_IPC2581* job = dynamic_cast<JOB_EXPORT_PCB_IPC2581*>( aJob );
  1855. if( job == nullptr )
  1856. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1857. BOARD* brd = getBoard( job->m_filename );
  1858. if( !brd )
  1859. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  1860. aJob->SetTitleBlock( brd->GetTitleBlock() );
  1861. if( job->GetConfiguredOutputPath().IsEmpty() )
  1862. {
  1863. wxFileName fn = brd->GetFileName();
  1864. fn.SetName( fn.GetName() );
  1865. fn.SetExt( FILEEXT::Ipc2581FileExtension );
  1866. job->SetWorkingOutputPath( fn.GetName() );
  1867. }
  1868. wxString outPath = job->GetFullOutputPath( brd->GetProject() );
  1869. if( !PATHS::EnsurePathExists( outPath, true ) )
  1870. {
  1871. m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
  1872. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1873. }
  1874. std::map<std::string, UTF8> props;
  1875. props["units"] = job->m_units == JOB_EXPORT_PCB_IPC2581::IPC2581_UNITS::MM ? "mm" : "inch";
  1876. props["sigfig"] = wxString::Format( "%d", job->m_precision );
  1877. props["version"] = job->m_version == JOB_EXPORT_PCB_IPC2581::IPC2581_VERSION::C ? "C" : "B";
  1878. props["OEMRef"] = job->m_colInternalId;
  1879. props["mpn"] = job->m_colMfgPn;
  1880. props["mfg"] = job->m_colMfg;
  1881. props["dist"] = job->m_colDist;
  1882. props["distpn"] = job->m_colDistPn;
  1883. wxString tempFile = wxFileName::CreateTempFileName( wxS( "pcbnew_ipc" ) );
  1884. try
  1885. {
  1886. IO_RELEASER<PCB_IO> pi( PCB_IO_MGR::PluginFind( PCB_IO_MGR::IPC2581 ) );
  1887. pi->SetProgressReporter( m_progressReporter );
  1888. pi->SaveBoard( tempFile, brd, &props );
  1889. }
  1890. catch( const IO_ERROR& ioe )
  1891. {
  1892. m_reporter->Report( wxString::Format( _( "Error generating IPC-2581 file '%s'.\n%s" ),
  1893. job->m_filename,
  1894. ioe.What() ),
  1895. RPT_SEVERITY_ERROR );
  1896. wxRemoveFile( tempFile );
  1897. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1898. }
  1899. if( job->m_compress )
  1900. {
  1901. wxFileName tempfn = outPath;
  1902. tempfn.SetExt( FILEEXT::Ipc2581FileExtension );
  1903. wxFileName zipfn = tempFile;
  1904. zipfn.SetExt( "zip" );
  1905. {
  1906. wxFFileOutputStream fnout( zipfn.GetFullPath() );
  1907. wxZipOutputStream zip( fnout );
  1908. wxFFileInputStream fnin( tempFile );
  1909. zip.PutNextEntry( tempfn.GetFullName() );
  1910. fnin.Read( zip );
  1911. }
  1912. wxRemoveFile( tempFile );
  1913. tempFile = zipfn.GetFullPath();
  1914. }
  1915. // If save succeeded, replace the original with what we just wrote
  1916. if( !wxRenameFile( tempFile, outPath ) )
  1917. {
  1918. m_reporter->Report( wxString::Format( _( "Error generating IPC-2581 file '%s'.\n"
  1919. "Failed to rename temporary file '%s." ),
  1920. outPath,
  1921. tempFile ),
  1922. RPT_SEVERITY_ERROR );
  1923. }
  1924. return CLI::EXIT_CODES::SUCCESS;
  1925. }
  1926. int PCBNEW_JOBS_HANDLER::JobExportIpcD356( JOB* aJob )
  1927. {
  1928. JOB_EXPORT_PCB_IPCD356* job = dynamic_cast<JOB_EXPORT_PCB_IPCD356*>( aJob );
  1929. if( job == nullptr )
  1930. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1931. BOARD* brd = getBoard( job->m_filename );
  1932. if( !brd )
  1933. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  1934. aJob->SetTitleBlock( brd->GetTitleBlock() );
  1935. if( job->GetConfiguredOutputPath().IsEmpty() )
  1936. {
  1937. wxFileName fn = brd->GetFileName();
  1938. fn.SetName( fn.GetName() );
  1939. fn.SetExt( FILEEXT::IpcD356FileExtension );
  1940. job->SetWorkingOutputPath( fn.GetFullName() );
  1941. }
  1942. wxString outPath = job->GetFullOutputPath( brd->GetProject() );
  1943. if( !PATHS::EnsurePathExists( outPath, true ) )
  1944. {
  1945. m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
  1946. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1947. }
  1948. IPC356D_WRITER exporter( brd );
  1949. bool success = exporter.Write( outPath );
  1950. if( success )
  1951. {
  1952. m_reporter->Report( _( "Successfully created IPC-D-356 file\n" ), RPT_SEVERITY_INFO );
  1953. return CLI::EXIT_CODES::SUCCESS;
  1954. }
  1955. else
  1956. {
  1957. m_reporter->Report( _( "Failed to create IPC-D-356 file\n" ), RPT_SEVERITY_ERROR );
  1958. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1959. }
  1960. }
  1961. int PCBNEW_JOBS_HANDLER::JobExportOdb( JOB* aJob )
  1962. {
  1963. JOB_EXPORT_PCB_ODB* job = dynamic_cast<JOB_EXPORT_PCB_ODB*>( aJob );
  1964. if( job == nullptr )
  1965. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1966. BOARD* brd = getBoard( job->m_filename );
  1967. if( !brd )
  1968. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  1969. aJob->SetTitleBlock( brd->GetTitleBlock() );
  1970. wxString path = job->GetConfiguredOutputPath();
  1971. if( job->GetConfiguredOutputPath().IsEmpty() )
  1972. {
  1973. if( job->m_compressionMode == JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::NONE )
  1974. {
  1975. // just basic folder name
  1976. job->SetWorkingOutputPath( "odb" );
  1977. }
  1978. else
  1979. {
  1980. wxFileName fn( brd->GetFileName() );
  1981. fn.SetName( fn.GetName() + wxS( "-odb" ) );
  1982. switch( job->m_compressionMode )
  1983. {
  1984. case JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::ZIP:
  1985. fn.SetExt( FILEEXT::ArchiveFileExtension );
  1986. break;
  1987. case JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::TGZ:
  1988. fn.SetExt( "tgz" );
  1989. break;
  1990. default:
  1991. break;
  1992. };
  1993. job->SetWorkingOutputPath( fn.GetFullName() );
  1994. }
  1995. }
  1996. DIALOG_EXPORT_ODBPP::GenerateODBPPFiles( *job, brd, nullptr, m_progressReporter, m_reporter );
  1997. return CLI::EXIT_CODES::SUCCESS;
  1998. }
  1999. int PCBNEW_JOBS_HANDLER::JobUpgrade( JOB* aJob )
  2000. {
  2001. JOB_PCB_UPGRADE* job = dynamic_cast<JOB_PCB_UPGRADE*>( aJob );
  2002. if( job == nullptr )
  2003. return CLI::EXIT_CODES::ERR_UNKNOWN;
  2004. bool shouldSave = job->m_force;
  2005. try
  2006. {
  2007. IO_RELEASER<PCB_IO> pi( PCB_IO_MGR::PluginFind( PCB_IO_MGR::KICAD_SEXP ) );
  2008. BOARD* brd = getBoard( job->m_filename );
  2009. if( brd->GetFileFormatVersionAtLoad() < SEXPR_BOARD_FILE_VERSION )
  2010. shouldSave = true;
  2011. if( shouldSave )
  2012. {
  2013. pi->SaveBoard( brd->GetFileName(), brd );
  2014. m_reporter->Report( _( "Successfully saved board file using the latest format\n" ), RPT_SEVERITY_INFO );
  2015. }
  2016. else
  2017. {
  2018. m_reporter->Report( _( "Board file was not updated\n" ), RPT_SEVERITY_ERROR );
  2019. }
  2020. }
  2021. catch( const IO_ERROR& ioe )
  2022. {
  2023. wxString msg =
  2024. wxString::Format( _( "Error saving board file '%s'.\n%s" ), job->m_filename, ioe.What().GetData() );
  2025. m_reporter->Report( msg, RPT_SEVERITY_ERROR );
  2026. return CLI::EXIT_CODES::ERR_UNKNOWN;
  2027. }
  2028. return CLI::EXIT_CODES::SUCCESS;
  2029. }
  2030. DS_PROXY_VIEW_ITEM* PCBNEW_JOBS_HANDLER::getDrawingSheetProxyView( BOARD* aBrd )
  2031. {
  2032. DS_PROXY_VIEW_ITEM* drawingSheet = new DS_PROXY_VIEW_ITEM( pcbIUScale,
  2033. &aBrd->GetPageSettings(),
  2034. aBrd->GetProject(),
  2035. &aBrd->GetTitleBlock(),
  2036. &aBrd->GetProperties() );
  2037. drawingSheet->SetSheetName( std::string() );
  2038. drawingSheet->SetSheetPath( std::string() );
  2039. drawingSheet->SetIsFirstPage( true );
  2040. drawingSheet->SetFileName( TO_UTF8( aBrd->GetFileName() ) );
  2041. return drawingSheet;
  2042. }
  2043. void PCBNEW_JOBS_HANDLER::loadOverrideDrawingSheet( BOARD* aBrd, const wxString& aSheetPath )
  2044. {
  2045. // dont bother attempting to load a empty path, if there was one
  2046. if( aSheetPath.IsEmpty() )
  2047. return;
  2048. auto loadSheet =
  2049. [&]( const wxString& path ) -> bool
  2050. {
  2051. BASE_SCREEN::m_DrawingSheetFileName = path;
  2052. FILENAME_RESOLVER resolver;
  2053. resolver.SetProject( aBrd->GetProject() );
  2054. resolver.SetProgramBase( &Pgm() );
  2055. wxString filename = resolver.ResolvePath( BASE_SCREEN::m_DrawingSheetFileName,
  2056. aBrd->GetProject()->GetProjectPath(),
  2057. { aBrd->GetEmbeddedFiles() } );
  2058. wxString msg;
  2059. if( !DS_DATA_MODEL::GetTheInstance().LoadDrawingSheet( filename, &msg ) )
  2060. {
  2061. m_reporter->Report( wxString::Format( _( "Error loading drawing sheet '%s'." ),
  2062. path )
  2063. + wxS( "\n" ) + msg + wxS( "\n" ),
  2064. RPT_SEVERITY_ERROR );
  2065. return false;
  2066. }
  2067. return true;
  2068. };
  2069. if( loadSheet( aSheetPath ) )
  2070. return;
  2071. // failed loading custom path, revert back to default
  2072. loadSheet( aBrd->GetProject()->GetProjectFile().m_BoardDrawingSheetFile );
  2073. }