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.

2336 lines
85 KiB

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