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.

2328 lines
85 KiB

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