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.

822 lines
28 KiB

  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 (C) 1992-2023 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 "pcbnew_jobs_handler.h"
  21. #include <jobs/job_fp_export_svg.h>
  22. #include <jobs/job_fp_upgrade.h>
  23. #include <jobs/job_export_pcb_gerber.h>
  24. #include <jobs/job_export_pcb_gerbers.h>
  25. #include <jobs/job_export_pcb_drill.h>
  26. #include <jobs/job_export_pcb_dxf.h>
  27. #include <jobs/job_export_pcb_pdf.h>
  28. #include <jobs/job_export_pcb_pos.h>
  29. #include <jobs/job_export_pcb_svg.h>
  30. #include <jobs/job_export_pcb_step.h>
  31. #include <cli/exit_codes.h>
  32. #include <plotters/plotter_dxf.h>
  33. #include <plotters/plotter_gerber.h>
  34. #include <plotters/plotters_pslike.h>
  35. #include <exporters/place_file_exporter.h>
  36. #include <exporters/step/exporter_step.h>
  37. #include "gerber_placefile_writer.h"
  38. #include <pgm_base.h>
  39. #include <pcbplot.h>
  40. #include <board_design_settings.h>
  41. #include <pad.h>
  42. #include <pcbnew_settings.h>
  43. #include <wx/dir.h>
  44. #include <pcb_plot_svg.h>
  45. #include <gendrill_Excellon_writer.h>
  46. #include <gendrill_gerber_writer.h>
  47. #include <wildcards_and_files_ext.h>
  48. #include <plugins/kicad/pcb_plugin.h>
  49. #include <gerber_jobfile_writer.h>
  50. #include <reporter.h>
  51. #include "pcbnew_scripting_helpers.h"
  52. PCBNEW_JOBS_HANDLER::PCBNEW_JOBS_HANDLER()
  53. {
  54. Register( "step",
  55. std::bind( &PCBNEW_JOBS_HANDLER::JobExportStep, this, std::placeholders::_1 ) );
  56. Register( "svg", std::bind( &PCBNEW_JOBS_HANDLER::JobExportSvg, this, std::placeholders::_1 ) );
  57. Register( "dxf", std::bind( &PCBNEW_JOBS_HANDLER::JobExportDxf, this, std::placeholders::_1 ) );
  58. Register( "pdf", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPdf, this, std::placeholders::_1 ) );
  59. Register( "gerber",
  60. std::bind( &PCBNEW_JOBS_HANDLER::JobExportGerber, this, std::placeholders::_1 ) );
  61. Register( "gerbers",
  62. std::bind( &PCBNEW_JOBS_HANDLER::JobExportGerbers, this, std::placeholders::_1 ) );
  63. Register( "drill",
  64. std::bind( &PCBNEW_JOBS_HANDLER::JobExportDrill, this, std::placeholders::_1 ) );
  65. Register( "pos", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPos, this, std::placeholders::_1 ) );
  66. Register( "fpupgrade",
  67. std::bind( &PCBNEW_JOBS_HANDLER::JobExportFpUpgrade, this, std::placeholders::_1 ) );
  68. Register( "fpsvg",
  69. std::bind( &PCBNEW_JOBS_HANDLER::JobExportFpSvg, this, std::placeholders::_1 ) );
  70. }
  71. int PCBNEW_JOBS_HANDLER::JobExportStep( JOB* aJob )
  72. {
  73. JOB_EXPORT_PCB_STEP* aStepJob = dynamic_cast<JOB_EXPORT_PCB_STEP*>( aJob );
  74. if( aStepJob == nullptr )
  75. return CLI::EXIT_CODES::ERR_UNKNOWN;
  76. if( aJob->IsCli() )
  77. m_reporter->Report( _( "Loading board\n" ), RPT_SEVERITY_INFO );
  78. BOARD* brd = LoadBoard( aStepJob->m_filename );
  79. if( aStepJob->m_outputFile.IsEmpty() )
  80. {
  81. wxFileName fn = brd->GetFileName();
  82. fn.SetName( fn.GetName() );
  83. fn.SetExt( wxS( "step" ) );
  84. aStepJob->m_outputFile = fn.GetFullName();
  85. }
  86. EXPORTER_STEP_PARAMS params;
  87. params.m_exportTracks = aStepJob->m_exportTracks;
  88. params.m_includeUnspecified = aStepJob->m_includeUnspecified;
  89. params.m_includeDNP = aStepJob->m_includeDNP;
  90. params.m_BoardOutlinesChainingEpsilon = aStepJob->m_BoardOutlinesChainingEpsilon;
  91. params.m_overwrite = aStepJob->m_overwrite;
  92. params.m_substModels = aStepJob->m_substModels;
  93. params.m_origin = VECTOR2D( aStepJob->m_xOrigin, aStepJob->m_yOrigin );
  94. params.m_useDrillOrigin = aStepJob->m_useDrillOrigin;
  95. params.m_useGridOrigin = aStepJob->m_useGridOrigin;
  96. params.m_boardOnly = aStepJob->m_boardOnly;
  97. EXPORTER_STEP stepExporter( brd, params );
  98. stepExporter.m_outputFile = aStepJob->m_outputFile;
  99. if( !stepExporter.Export() )
  100. {
  101. return CLI::EXIT_CODES::ERR_UNKNOWN;
  102. }
  103. return CLI::EXIT_CODES::OK;
  104. }
  105. int PCBNEW_JOBS_HANDLER::JobExportSvg( JOB* aJob )
  106. {
  107. JOB_EXPORT_PCB_SVG* aSvgJob = dynamic_cast<JOB_EXPORT_PCB_SVG*>( aJob );
  108. if( aSvgJob == nullptr )
  109. return CLI::EXIT_CODES::ERR_UNKNOWN;
  110. PCB_PLOT_SVG_OPTIONS svgPlotOptions;
  111. svgPlotOptions.m_blackAndWhite = aSvgJob->m_blackAndWhite;
  112. svgPlotOptions.m_colorTheme = aSvgJob->m_colorTheme;
  113. svgPlotOptions.m_outputFile = aSvgJob->m_outputFile;
  114. svgPlotOptions.m_mirror = aSvgJob->m_mirror;
  115. svgPlotOptions.m_negative = aSvgJob->m_negative;
  116. svgPlotOptions.m_pageSizeMode = aSvgJob->m_pageSizeMode;
  117. svgPlotOptions.m_printMaskLayer = aSvgJob->m_printMaskLayer;
  118. svgPlotOptions.m_plotFrame = aSvgJob->m_plotDrawingSheet;
  119. if( aJob->IsCli() )
  120. m_reporter->Report( _( "Loading board\n" ), RPT_SEVERITY_INFO );
  121. BOARD* brd = LoadBoard( aSvgJob->m_filename );
  122. if( aJob->IsCli() )
  123. {
  124. if( PCB_PLOT_SVG::Plot( brd, svgPlotOptions ) )
  125. m_reporter->Report( _( "Successfully created svg file" ), RPT_SEVERITY_INFO );
  126. else
  127. m_reporter->Report( _( "Error creating svg file" ), RPT_SEVERITY_ERROR );
  128. }
  129. return CLI::EXIT_CODES::OK;
  130. }
  131. int PCBNEW_JOBS_HANDLER::JobExportDxf( JOB* aJob )
  132. {
  133. JOB_EXPORT_PCB_DXF* aDxfJob = dynamic_cast<JOB_EXPORT_PCB_DXF*>( aJob );
  134. if( aDxfJob == nullptr )
  135. return CLI::EXIT_CODES::ERR_UNKNOWN;
  136. if( aJob->IsCli() )
  137. m_reporter->Report( _( "Loading board\n" ), RPT_SEVERITY_INFO );
  138. BOARD* brd = LoadBoard( aDxfJob->m_filename );
  139. if( aDxfJob->m_outputFile.IsEmpty() )
  140. {
  141. wxFileName fn = brd->GetFileName();
  142. fn.SetName( fn.GetName() );
  143. fn.SetExt( GetDefaultPlotExtension( PLOT_FORMAT::DXF ) );
  144. aDxfJob->m_outputFile = fn.GetFullName();
  145. }
  146. PCB_PLOT_PARAMS plotOpts;
  147. plotOpts.SetFormat( PLOT_FORMAT::DXF );
  148. plotOpts.SetDXFPlotPolygonMode( aDxfJob->m_plotGraphicItemsUsingContours );
  149. if( aDxfJob->m_dxfUnits == JOB_EXPORT_PCB_DXF::DXF_UNITS::MILLIMETERS )
  150. {
  151. plotOpts.SetDXFPlotUnits( DXF_UNITS::MILLIMETERS );
  152. }
  153. else
  154. {
  155. plotOpts.SetDXFPlotUnits( DXF_UNITS::INCHES );
  156. }
  157. plotOpts.SetPlotFrameRef( aDxfJob->m_plotBorderTitleBlocks );
  158. plotOpts.SetPlotValue( aDxfJob->m_plotFootprintValues );
  159. plotOpts.SetPlotReference( aDxfJob->m_plotRefDes );
  160. plotOpts.SetLayerSelection( aDxfJob->m_printMaskLayer );
  161. DXF_PLOTTER* plotter = (DXF_PLOTTER*) StartPlotBoard(
  162. brd, &plotOpts, UNDEFINED_LAYER, aDxfJob->m_outputFile, wxEmptyString, wxEmptyString );
  163. if( plotter )
  164. {
  165. PlotBoardLayers( brd, plotter, aDxfJob->m_printMaskLayer.SeqStackupBottom2Top(), plotOpts );
  166. plotter->EndPlot();
  167. }
  168. delete plotter;
  169. return CLI::EXIT_CODES::OK;
  170. }
  171. int PCBNEW_JOBS_HANDLER::JobExportPdf( JOB* aJob )
  172. {
  173. JOB_EXPORT_PCB_PDF* aPdfJob = dynamic_cast<JOB_EXPORT_PCB_PDF*>( aJob );
  174. if( aPdfJob == nullptr )
  175. return CLI::EXIT_CODES::ERR_UNKNOWN;
  176. if( aJob->IsCli() )
  177. m_reporter->Report( _( "Loading board\n" ), RPT_SEVERITY_INFO );
  178. BOARD* brd = LoadBoard( aPdfJob->m_filename );
  179. if( aPdfJob->m_outputFile.IsEmpty() )
  180. {
  181. wxFileName fn = brd->GetFileName();
  182. fn.SetName( fn.GetName() );
  183. fn.SetExt( GetDefaultPlotExtension( PLOT_FORMAT::PDF ) );
  184. aPdfJob->m_outputFile = fn.GetFullName();
  185. }
  186. PCB_PLOT_PARAMS plotOpts;
  187. plotOpts.SetFormat( PLOT_FORMAT::PDF );
  188. plotOpts.SetPlotFrameRef( aPdfJob->m_plotBorderTitleBlocks );
  189. plotOpts.SetPlotValue( aPdfJob->m_plotFootprintValues );
  190. plotOpts.SetPlotReference( aPdfJob->m_plotRefDes );
  191. plotOpts.SetLayerSelection( aPdfJob->m_printMaskLayer );
  192. SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
  193. plotOpts.SetColorSettings( mgr.GetColorSettings( aPdfJob->m_colorTheme ) );
  194. plotOpts.SetMirror( aPdfJob->m_mirror );
  195. plotOpts.SetBlackAndWhite( aPdfJob->m_blackAndWhite );
  196. plotOpts.SetNegative( aPdfJob->m_negative );
  197. PDF_PLOTTER* plotter = (PDF_PLOTTER*) StartPlotBoard(
  198. brd, &plotOpts, UNDEFINED_LAYER, aPdfJob->m_outputFile, wxEmptyString, wxEmptyString );
  199. if( plotter )
  200. {
  201. PlotBoardLayers( brd, plotter, aPdfJob->m_printMaskLayer.SeqStackupBottom2Top(), plotOpts );
  202. PlotInteractiveLayer( brd, plotter, plotOpts );
  203. plotter->EndPlot();
  204. }
  205. delete plotter;
  206. return CLI::EXIT_CODES::OK;
  207. }
  208. int PCBNEW_JOBS_HANDLER::JobExportGerbers( JOB* aJob )
  209. {
  210. int exitCode = CLI::EXIT_CODES::OK;
  211. JOB_EXPORT_PCB_GERBERS* aGerberJob = dynamic_cast<JOB_EXPORT_PCB_GERBERS*>( aJob );
  212. if( aGerberJob == nullptr )
  213. return CLI::EXIT_CODES::ERR_UNKNOWN;
  214. if( aJob->IsCli() )
  215. m_reporter->Report( _( "Loading board\n" ), RPT_SEVERITY_INFO );
  216. BOARD* brd = LoadBoard( aGerberJob->m_filename );
  217. PCB_PLOT_PARAMS boardPlotOptions = brd->GetPlotOptions();
  218. LSET plotOnAllLayersSelection = boardPlotOptions.GetPlotOnAllLayersSelection();
  219. GERBER_JOBFILE_WRITER jobfile_writer( brd );
  220. wxString fileExt;
  221. if( aGerberJob->m_useBoardPlotParams )
  222. {
  223. aGerberJob->m_printMaskLayer = boardPlotOptions.GetLayerSelection();
  224. aGerberJob->m_layersIncludeOnAll = boardPlotOptions.GetPlotOnAllLayersSelection();
  225. }
  226. else
  227. {
  228. // default to the board enabled layers
  229. if( aGerberJob->m_printMaskLayer == 0 )
  230. aGerberJob->m_printMaskLayer = brd->GetEnabledLayers();
  231. if( aGerberJob->m_layersIncludeOnAllSet )
  232. aGerberJob->m_layersIncludeOnAll = plotOnAllLayersSelection;
  233. }
  234. for( LSEQ seq = aGerberJob->m_printMaskLayer.UIOrder(); seq; ++seq )
  235. {
  236. LSEQ plotSequence;
  237. // Base layer always gets plotted first.
  238. plotSequence.push_back( *seq );
  239. // Now all the "include on all" layers
  240. for( LSEQ seqAll = aGerberJob->m_layersIncludeOnAll.UIOrder(); seqAll; ++seqAll )
  241. {
  242. // Don't plot the same layer more than once;
  243. if( find( plotSequence.begin(), plotSequence.end(), *seqAll ) != plotSequence.end() )
  244. continue;
  245. plotSequence.push_back( *seqAll );
  246. }
  247. // Pick the basename from the board file
  248. wxFileName fn( brd->GetFileName() );
  249. PCB_LAYER_ID layer = *seq;
  250. PCB_PLOT_PARAMS plotOpts;
  251. if( aGerberJob->m_useBoardPlotParams )
  252. plotOpts = boardPlotOptions;
  253. else
  254. populateGerberPlotOptionsFromJob( plotOpts, aGerberJob );
  255. if( plotOpts.GetUseGerberProtelExtensions() )
  256. fileExt = GetGerberProtelExtension( layer );
  257. else
  258. fileExt = GerberFileExtension;
  259. BuildPlotFileName( &fn, aGerberJob->m_outputFile, brd->GetLayerName( layer ), fileExt );
  260. wxString fullname = fn.GetFullName();
  261. jobfile_writer.AddGbrFile( layer, fullname );
  262. // We are feeding it one layer at the start here to silence a logic check
  263. GERBER_PLOTTER* plotter = (GERBER_PLOTTER*) StartPlotBoard(
  264. brd, &plotOpts, layer, fn.GetFullPath(), wxEmptyString, wxEmptyString );
  265. if( plotter )
  266. {
  267. m_reporter->Report( wxString::Format( _( "Plotted to '%s'.\n" ), fn.GetFullPath() ),
  268. RPT_SEVERITY_ACTION );
  269. PlotBoardLayers( brd, plotter, plotSequence, plotOpts );
  270. plotter->EndPlot();
  271. }
  272. else
  273. {
  274. m_reporter->Report( wxString::Format( _( "Failed to plot to '%s'.\n" ),
  275. fn.GetFullPath() ),
  276. RPT_SEVERITY_ERROR );
  277. exitCode = CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  278. }
  279. delete plotter;
  280. }
  281. wxFileName fn( aGerberJob->m_filename );
  282. // Build gerber job file from basename
  283. BuildPlotFileName( &fn, aGerberJob->m_outputFile, wxT( "job" ), GerberJobFileExtension );
  284. jobfile_writer.CreateJobFile( fn.GetFullPath() );
  285. return exitCode;
  286. }
  287. void PCBNEW_JOBS_HANDLER::populateGerberPlotOptionsFromJob( PCB_PLOT_PARAMS& aPlotOpts,
  288. JOB_EXPORT_PCB_GERBER* aJob )
  289. {
  290. aPlotOpts.SetFormat( PLOT_FORMAT::GERBER );
  291. aPlotOpts.SetPlotFrameRef( aJob->m_plotBorderTitleBlocks );
  292. aPlotOpts.SetPlotValue( aJob->m_plotFootprintValues );
  293. aPlotOpts.SetPlotReference( aJob->m_plotRefDes );
  294. aPlotOpts.SetSubtractMaskFromSilk( aJob->m_subtractSolderMaskFromSilk );
  295. // Always disable plot pad holes
  296. aPlotOpts.SetDrillMarksType( DRILL_MARKS::NO_DRILL_SHAPE );
  297. aPlotOpts.SetDisableGerberMacros( aJob->m_disableApertureMacros );
  298. aPlotOpts.SetUseGerberX2format( aJob->m_useX2Format );
  299. aPlotOpts.SetIncludeGerberNetlistInfo( aJob->m_includeNetlistAttributes );
  300. aPlotOpts.SetUseAuxOrigin( aJob->m_useAuxOrigin );
  301. aPlotOpts.SetUseGerberProtelExtensions( aJob->m_useProtelFileExtension );
  302. aPlotOpts.SetGerberPrecision( aJob->m_precision );
  303. }
  304. int PCBNEW_JOBS_HANDLER::JobExportGerber( JOB* aJob )
  305. {
  306. int exitCode = CLI::EXIT_CODES::OK;
  307. JOB_EXPORT_PCB_GERBER* aGerberJob = dynamic_cast<JOB_EXPORT_PCB_GERBER*>( aJob );
  308. if( aGerberJob == nullptr )
  309. return CLI::EXIT_CODES::ERR_UNKNOWN;
  310. if( aJob->IsCli() )
  311. m_reporter->Report( _( "Loading board\n" ), RPT_SEVERITY_INFO );
  312. BOARD* brd = LoadBoard( aGerberJob->m_filename );
  313. if( aGerberJob->m_outputFile.IsEmpty() )
  314. {
  315. wxFileName fn = brd->GetFileName();
  316. fn.SetName( fn.GetName() );
  317. fn.SetExt( GetDefaultPlotExtension( PLOT_FORMAT::GERBER ) );
  318. aGerberJob->m_outputFile = fn.GetFullName();
  319. }
  320. PCB_PLOT_PARAMS plotOpts;
  321. populateGerberPlotOptionsFromJob( plotOpts, aGerberJob );
  322. plotOpts.SetLayerSelection( aGerberJob->m_printMaskLayer );
  323. // We are feeding it one layer at the start here to silence a logic check
  324. GERBER_PLOTTER* plotter = (GERBER_PLOTTER*) StartPlotBoard(
  325. brd, &plotOpts, aGerberJob->m_printMaskLayer.Seq().front(), aGerberJob->m_outputFile,
  326. wxEmptyString, wxEmptyString );
  327. if( plotter )
  328. {
  329. PlotBoardLayers( brd, plotter, aGerberJob->m_printMaskLayer.SeqStackupBottom2Top(),
  330. plotOpts );
  331. plotter->EndPlot();
  332. }
  333. else
  334. {
  335. m_reporter->Report( wxString::Format( _( "Failed to plot to '%s'.\n" ),
  336. aGerberJob->m_outputFile ),
  337. RPT_SEVERITY_ERROR );
  338. exitCode = CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  339. }
  340. delete plotter;
  341. return exitCode;
  342. }
  343. static DRILL_PRECISION precisionListForInches( 2, 4 );
  344. static DRILL_PRECISION precisionListForMetric( 3, 3 );
  345. int PCBNEW_JOBS_HANDLER::JobExportDrill( JOB* aJob )
  346. {
  347. JOB_EXPORT_PCB_DRILL* aDrillJob = dynamic_cast<JOB_EXPORT_PCB_DRILL*>( aJob );
  348. if( aDrillJob == nullptr )
  349. return CLI::EXIT_CODES::ERR_UNKNOWN;
  350. if( aJob->IsCli() )
  351. m_reporter->Report( _( "Loading board\n" ), RPT_SEVERITY_INFO );
  352. BOARD* brd = LoadBoard( aDrillJob->m_filename );
  353. std::unique_ptr<GENDRILL_WRITER_BASE> drillWriter;
  354. if( aDrillJob->m_format == JOB_EXPORT_PCB_DRILL::DRILL_FORMAT::EXCELLON )
  355. {
  356. drillWriter = std::make_unique<EXCELLON_WRITER>( brd );
  357. }
  358. else
  359. {
  360. drillWriter = std::make_unique<GERBER_WRITER>( brd );
  361. }
  362. VECTOR2I offset;
  363. if( aDrillJob->m_drillOrigin == JOB_EXPORT_PCB_DRILL::DRILL_ORIGIN::ABSOLUTE )
  364. offset = VECTOR2I( 0, 0 );
  365. else
  366. offset = brd->GetDesignSettings().GetAuxOrigin();
  367. PLOT_FORMAT mapFormat = PLOT_FORMAT::PDF;
  368. switch( aDrillJob->m_mapFormat )
  369. {
  370. case JOB_EXPORT_PCB_DRILL::MAP_FORMAT::POSTSCRIPT: mapFormat = PLOT_FORMAT::POST; break;
  371. case JOB_EXPORT_PCB_DRILL::MAP_FORMAT::GERBER_X2: mapFormat = PLOT_FORMAT::GERBER; break;
  372. case JOB_EXPORT_PCB_DRILL::MAP_FORMAT::DXF: mapFormat = PLOT_FORMAT::DXF; break;
  373. case JOB_EXPORT_PCB_DRILL::MAP_FORMAT::SVG: mapFormat = PLOT_FORMAT::SVG; break;
  374. default:
  375. case JOB_EXPORT_PCB_DRILL::MAP_FORMAT::PDF: mapFormat = PLOT_FORMAT::PDF; break;
  376. }
  377. if( aDrillJob->m_format == JOB_EXPORT_PCB_DRILL::DRILL_FORMAT::EXCELLON )
  378. {
  379. EXCELLON_WRITER::ZEROS_FMT zeroFmt;
  380. switch( aDrillJob->m_zeroFormat )
  381. {
  382. case JOB_EXPORT_PCB_DRILL::ZEROS_FORMAT::KEEP_ZEROS:
  383. zeroFmt = EXCELLON_WRITER::KEEP_ZEROS;
  384. break;
  385. case JOB_EXPORT_PCB_DRILL::ZEROS_FORMAT::SUPPRESS_LEADING:
  386. zeroFmt = EXCELLON_WRITER::SUPPRESS_LEADING;
  387. break;
  388. case JOB_EXPORT_PCB_DRILL::ZEROS_FORMAT::SUPPRESS_TRAILING:
  389. zeroFmt = EXCELLON_WRITER::SUPPRESS_TRAILING;
  390. break;
  391. case JOB_EXPORT_PCB_DRILL::ZEROS_FORMAT::DECIMAL:
  392. default:
  393. zeroFmt = EXCELLON_WRITER::DECIMAL_FORMAT;
  394. break;
  395. }
  396. DRILL_PRECISION precision;
  397. if( aDrillJob->m_drillUnits == JOB_EXPORT_PCB_DRILL::DRILL_UNITS::INCHES )
  398. precision = precisionListForInches;
  399. else
  400. precision = precisionListForMetric;
  401. EXCELLON_WRITER* excellonWriter = dynamic_cast<EXCELLON_WRITER*>( drillWriter.get() );
  402. if( excellonWriter == nullptr )
  403. return CLI::EXIT_CODES::ERR_UNKNOWN;
  404. excellonWriter->SetFormat( aDrillJob->m_drillUnits
  405. == JOB_EXPORT_PCB_DRILL::DRILL_UNITS::MILLIMETERS,
  406. zeroFmt, precision.m_Lhs, precision.m_Rhs );
  407. excellonWriter->SetOptions( aDrillJob->m_excellonMirrorY,
  408. aDrillJob->m_excellonMinimalHeader,
  409. offset, aDrillJob->m_excellonCombinePTHNPTH );
  410. excellonWriter->SetRouteModeForOvalHoles( aDrillJob->m_excellonOvalDrillRoute );
  411. excellonWriter->SetMapFileFormat( mapFormat );
  412. if( !excellonWriter->CreateDrillandMapFilesSet( aDrillJob->m_outputDir, true,
  413. aDrillJob->m_generateMap, m_reporter ) )
  414. {
  415. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  416. }
  417. }
  418. else if( aDrillJob->m_format == JOB_EXPORT_PCB_DRILL::DRILL_FORMAT::GERBER )
  419. {
  420. GERBER_WRITER* gerberWriter = dynamic_cast<GERBER_WRITER*>( drillWriter.get() );
  421. if( gerberWriter == nullptr )
  422. return CLI::EXIT_CODES::ERR_UNKNOWN;
  423. // Set gerber precision: only 5 or 6 digits for mantissa are allowed
  424. // (SetFormat() accept 5 or 6, and any other value set the precision to 5)
  425. // the integer part precision is always 4, and units always mm
  426. gerberWriter->SetFormat( aDrillJob->m_gerberPrecision );
  427. gerberWriter->SetOptions( offset );
  428. gerberWriter->SetMapFileFormat( mapFormat );
  429. if( !gerberWriter->CreateDrillandMapFilesSet( aDrillJob->m_outputDir, true,
  430. aDrillJob->m_generateMap, m_reporter ) )
  431. {
  432. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  433. }
  434. }
  435. return CLI::EXIT_CODES::OK;
  436. }
  437. int PCBNEW_JOBS_HANDLER::JobExportPos( JOB* aJob )
  438. {
  439. JOB_EXPORT_PCB_POS* aPosJob = dynamic_cast<JOB_EXPORT_PCB_POS*>( aJob );
  440. if( aPosJob == nullptr )
  441. return CLI::EXIT_CODES::ERR_UNKNOWN;
  442. if( aJob->IsCli() )
  443. m_reporter->Report( _( "Loading board\n" ), RPT_SEVERITY_INFO );
  444. BOARD* brd = LoadBoard( aPosJob->m_filename );
  445. if( aPosJob->m_outputFile.IsEmpty() )
  446. {
  447. wxFileName fn = brd->GetFileName();
  448. fn.SetName( fn.GetName() );
  449. if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::ASCII )
  450. fn.SetExt( FootprintPlaceFileExtension );
  451. else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV )
  452. fn.SetExt( CsvFileExtension );
  453. else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::GERBER )
  454. fn.SetExt( GerberFileExtension );
  455. aPosJob->m_outputFile = fn.GetFullName();
  456. }
  457. if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::ASCII
  458. || aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV )
  459. {
  460. FILE* file = nullptr;
  461. file = wxFopen( aPosJob->m_outputFile, wxS( "wt" ) );
  462. if( file == nullptr )
  463. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  464. std::string data;
  465. bool frontSide = aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::FRONT
  466. || aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH;
  467. bool backSide = aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BACK
  468. || aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH;
  469. PLACE_FILE_EXPORTER exporter( brd,
  470. aPosJob->m_units == JOB_EXPORT_PCB_POS::UNITS::MILLIMETERS,
  471. aPosJob->m_smdOnly, aPosJob->m_excludeFootprintsWithTh,
  472. frontSide, backSide,
  473. aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV,
  474. aPosJob->m_useDrillPlaceFileOrigin,
  475. aPosJob->m_negateBottomX );
  476. data = exporter.GenPositionData();
  477. fputs( data.c_str(), file );
  478. fclose( file );
  479. }
  480. else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::GERBER )
  481. {
  482. PLACEFILE_GERBER_WRITER exporter( brd );
  483. PCB_LAYER_ID gbrLayer = F_Cu;
  484. if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BACK )
  485. gbrLayer = B_Cu;
  486. exporter.CreatePlaceFile( aPosJob->m_outputFile, gbrLayer, aPosJob->m_gerberBoardEdge );
  487. }
  488. return CLI::EXIT_CODES::OK;
  489. }
  490. extern FOOTPRINT* try_load_footprint( const wxFileName& aFileName, IO_MGR::PCB_FILE_T aFileType,
  491. const wxString& aName );
  492. int PCBNEW_JOBS_HANDLER::JobExportFpUpgrade( JOB* aJob )
  493. {
  494. JOB_FP_UPGRADE* upgradeJob = dynamic_cast<JOB_FP_UPGRADE*>( aJob );
  495. if( upgradeJob == nullptr )
  496. return CLI::EXIT_CODES::ERR_UNKNOWN;
  497. if( aJob->IsCli() )
  498. m_reporter->Report( _( "Loading footprint library\n" ), RPT_SEVERITY_INFO );
  499. if( !upgradeJob->m_outputLibraryPath.IsEmpty() )
  500. {
  501. if( wxFile::Exists( upgradeJob->m_outputLibraryPath ) ||
  502. wxDir::Exists( upgradeJob->m_outputLibraryPath) )
  503. {
  504. m_reporter->Report( _( "Output path must not conflict with existing path\n" ),
  505. RPT_SEVERITY_ERROR );
  506. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  507. }
  508. }
  509. PCB_PLUGIN pcb_io( CTL_FOR_LIBRARY );
  510. FP_CACHE fpLib( &pcb_io, upgradeJob->m_libraryPath );
  511. try
  512. {
  513. fpLib.Load();
  514. }
  515. catch(...)
  516. {
  517. m_reporter->Report( _( "Unable to load library\n" ), RPT_SEVERITY_ERROR );
  518. return CLI::EXIT_CODES::ERR_UNKNOWN;
  519. }
  520. bool shouldSave = upgradeJob->m_force;
  521. for( const auto& footprint : fpLib.GetFootprints() )
  522. {
  523. if( footprint.second->GetFootprint()->GetFileFormatVersionAtLoad() < SEXPR_BOARD_FILE_VERSION )
  524. {
  525. shouldSave = true;
  526. }
  527. }
  528. if( shouldSave )
  529. {
  530. m_reporter->Report( _( "Saving footprint library\n" ), RPT_SEVERITY_INFO );
  531. try
  532. {
  533. if( !upgradeJob->m_outputLibraryPath.IsEmpty() )
  534. {
  535. fpLib.SetPath( upgradeJob->m_outputLibraryPath );
  536. }
  537. fpLib.Save();
  538. }
  539. catch( ... )
  540. {
  541. m_reporter->Report( _( "Unable to save library\n" ), RPT_SEVERITY_ERROR );
  542. return CLI::EXIT_CODES::ERR_UNKNOWN;
  543. }
  544. }
  545. else
  546. {
  547. m_reporter->Report( _( "Footprint library was not updated\n" ), RPT_SEVERITY_INFO );
  548. }
  549. return CLI::EXIT_CODES::OK;
  550. }
  551. int PCBNEW_JOBS_HANDLER::JobExportFpSvg( JOB* aJob )
  552. {
  553. JOB_FP_EXPORT_SVG* svgJob = dynamic_cast<JOB_FP_EXPORT_SVG*>( aJob );
  554. if( svgJob == nullptr )
  555. return CLI::EXIT_CODES::ERR_UNKNOWN;
  556. if( aJob->IsCli() )
  557. m_reporter->Report( _( "Loading footprint library\n" ), RPT_SEVERITY_INFO );
  558. PCB_PLUGIN pcb_io( CTL_FOR_LIBRARY );
  559. FP_CACHE fpLib( &pcb_io, svgJob->m_libraryPath );
  560. try
  561. {
  562. fpLib.Load();
  563. }
  564. catch( ... )
  565. {
  566. m_reporter->Report( _( "Unable to load library\n" ), RPT_SEVERITY_ERROR );
  567. return CLI::EXIT_CODES::ERR_UNKNOWN;
  568. }
  569. if( !svgJob->m_outputDirectory.IsEmpty() && !wxDir::Exists( svgJob->m_outputDirectory ) )
  570. {
  571. wxFileName::Mkdir( svgJob->m_outputDirectory );
  572. }
  573. int exitCode = CLI::EXIT_CODES::OK;
  574. // Just plot all the symbols we can
  575. FP_CACHE_FOOTPRINT_MAP& footprintMap = fpLib.GetFootprints();
  576. bool singleFpPlotted = false;
  577. for( FP_CACHE_FOOTPRINT_MAP::iterator it = footprintMap.begin(); it != footprintMap.end();
  578. ++it )
  579. {
  580. const FOOTPRINT* fp = it->second->GetFootprint();
  581. if( !svgJob->m_footprint.IsEmpty() )
  582. {
  583. if( fp->GetFPID().GetLibItemName().wx_str() != svgJob->m_footprint )
  584. {
  585. // skip until we find the right footprint
  586. continue;
  587. }
  588. else
  589. {
  590. singleFpPlotted = true;
  591. }
  592. }
  593. exitCode = doFpExportSvg( svgJob, fp );
  594. if( exitCode != CLI::EXIT_CODES::OK )
  595. break;
  596. }
  597. if( !svgJob->m_footprint.IsEmpty() && !singleFpPlotted )
  598. {
  599. m_reporter->Report( _( "The given footprint could not be found to export." ),
  600. RPT_SEVERITY_ERROR );
  601. }
  602. return CLI::EXIT_CODES::OK;
  603. }
  604. int PCBNEW_JOBS_HANDLER::doFpExportSvg( JOB_FP_EXPORT_SVG* aSvgJob, const FOOTPRINT* aFootprint )
  605. {
  606. // the hack for now is we create fake boards containing the footprint and plot the board
  607. // until we refactor better plot api later
  608. std::unique_ptr<BOARD> brd;
  609. brd.reset( CreateEmptyBoard() );
  610. FOOTPRINT* fp = dynamic_cast<FOOTPRINT*>( aFootprint->Clone() );
  611. if( fp == nullptr )
  612. return CLI::EXIT_CODES::ERR_UNKNOWN;
  613. fp->SetLink( niluuid );
  614. fp->SetFlags( IS_NEW );
  615. fp->SetParent( brd.get() );
  616. for( PAD* pad : fp->Pads() )
  617. {
  618. pad->SetLocalRatsnestVisible( false );
  619. pad->SetNetCode( 0 );
  620. }
  621. fp->SetOrientation( ANGLE_0 );
  622. fp->SetPosition( VECTOR2I( 0, 0 ) );
  623. brd->Add( fp, ADD_MODE::INSERT, true );
  624. wxFileName outputFile;
  625. outputFile.SetPath( aSvgJob->m_outputDirectory );
  626. outputFile.SetName( aFootprint->GetFPID().GetLibItemName().wx_str() );
  627. outputFile.SetExt( SVGFileExtension );
  628. m_reporter->Report( wxString::Format( _( "Plotting footprint '%s' to '%s'\n" ),
  629. aFootprint->GetFPID().GetLibItemName().wx_str(),
  630. outputFile.GetFullPath() ),
  631. RPT_SEVERITY_ACTION );
  632. PCB_PLOT_SVG_OPTIONS svgPlotOptions;
  633. svgPlotOptions.m_blackAndWhite = aSvgJob->m_blackAndWhite;
  634. svgPlotOptions.m_colorTheme = aSvgJob->m_colorTheme;
  635. svgPlotOptions.m_outputFile = outputFile.GetFullPath();
  636. svgPlotOptions.m_mirror = false;
  637. svgPlotOptions.m_pageSizeMode = 2; // board bounding box
  638. svgPlotOptions.m_printMaskLayer = aSvgJob->m_printMaskLayer;
  639. svgPlotOptions.m_plotFrame = false;
  640. if( !PCB_PLOT_SVG::Plot( brd.get(), svgPlotOptions ) )
  641. m_reporter->Report( _( "Error creating svg file" ), RPT_SEVERITY_ERROR );
  642. return CLI::EXIT_CODES::OK;
  643. }