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.

1479 lines
53 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
  5. * Copyright (C) 1992-2024 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_gerber.h>
  32. #include <jobs/job_export_pcb_gerbers.h>
  33. #include <jobs/job_export_pcb_drill.h>
  34. #include <jobs/job_export_pcb_dxf.h>
  35. #include <jobs/job_export_pcb_pdf.h>
  36. #include <jobs/job_export_pcb_pos.h>
  37. #include <jobs/job_export_pcb_svg.h>
  38. #include <jobs/job_export_pcb_3d.h>
  39. #include <jobs/job_pcb_render.h>
  40. #include <jobs/job_pcb_drc.h>
  41. #include <cli/exit_codes.h>
  42. #include <exporters/place_file_exporter.h>
  43. #include <exporters/step/exporter_step.h>
  44. #include <plotters/plotter_dxf.h>
  45. #include <plotters/plotter_gerber.h>
  46. #include <plotters/plotters_pslike.h>
  47. #include <tool/tool_manager.h>
  48. #include <tools/drc_tool.h>
  49. #include <gerber_jobfile_writer.h>
  50. #include "gerber_placefile_writer.h"
  51. #include <gendrill_Excellon_writer.h>
  52. #include <gendrill_gerber_writer.h>
  53. #include <kiface_base.h>
  54. #include <macros.h>
  55. #include <pad.h>
  56. #include <pcb_marker.h>
  57. #include <project/project_file.h>
  58. #include <exporters/export_svg.h>
  59. #include <kiface_ids.h>
  60. #include <netlist_reader/pcb_netlist.h>
  61. #include <netlist_reader/netlist_reader.h>
  62. #include <pcbnew_settings.h>
  63. #include <pcbplot.h>
  64. #include <pgm_base.h>
  65. #include <3d_rendering/raytracing/render_3d_raytrace_ram.h>
  66. #include <3d_rendering/track_ball.h>
  67. #include <project_pcb.h>
  68. #include <pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h>
  69. #include <reporter.h>
  70. #include <string_utf8_map.h>
  71. #include <wildcards_and_files_ext.h>
  72. #include <export_vrml.h>
  73. #include <wx/wfstream.h>
  74. #include <wx/zipstrm.h>
  75. #include "pcbnew_scripting_helpers.h"
  76. #ifdef _WIN32
  77. #ifdef TRANSPARENT
  78. #undef TRANSPARENT
  79. #endif
  80. #endif
  81. PCBNEW_JOBS_HANDLER::PCBNEW_JOBS_HANDLER( KIWAY* aKiway ) :
  82. JOB_DISPATCHER( aKiway )
  83. {
  84. Register( "3d", std::bind( &PCBNEW_JOBS_HANDLER::JobExportStep, this, std::placeholders::_1 ) );
  85. Register( "render", std::bind( &PCBNEW_JOBS_HANDLER::JobExportRender, this, std::placeholders::_1 ) );
  86. Register( "svg", std::bind( &PCBNEW_JOBS_HANDLER::JobExportSvg, this, std::placeholders::_1 ) );
  87. Register( "dxf", std::bind( &PCBNEW_JOBS_HANDLER::JobExportDxf, this, std::placeholders::_1 ) );
  88. Register( "pdf", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPdf, this, std::placeholders::_1 ) );
  89. Register( "gerber",
  90. std::bind( &PCBNEW_JOBS_HANDLER::JobExportGerber, this, std::placeholders::_1 ) );
  91. Register( "gerbers",
  92. std::bind( &PCBNEW_JOBS_HANDLER::JobExportGerbers, this, std::placeholders::_1 ) );
  93. Register( "drill",
  94. std::bind( &PCBNEW_JOBS_HANDLER::JobExportDrill, this, std::placeholders::_1 ) );
  95. Register( "pos", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPos, this, std::placeholders::_1 ) );
  96. Register( "fpupgrade",
  97. std::bind( &PCBNEW_JOBS_HANDLER::JobExportFpUpgrade, this, std::placeholders::_1 ) );
  98. Register( "fpsvg",
  99. std::bind( &PCBNEW_JOBS_HANDLER::JobExportFpSvg, this, std::placeholders::_1 ) );
  100. Register( "drc", std::bind( &PCBNEW_JOBS_HANDLER::JobExportDrc, this, std::placeholders::_1 ) );
  101. Register( "ipc2581",
  102. std::bind( &PCBNEW_JOBS_HANDLER::JobExportIpc2581, this, std::placeholders::_1 ) );
  103. }
  104. int PCBNEW_JOBS_HANDLER::JobExportStep( JOB* aJob )
  105. {
  106. JOB_EXPORT_PCB_3D* aStepJob = dynamic_cast<JOB_EXPORT_PCB_3D*>( aJob );
  107. if( aStepJob == nullptr )
  108. return CLI::EXIT_CODES::ERR_UNKNOWN;
  109. if( aJob->IsCli() )
  110. m_reporter->Report( _( "Loading board\n" ), RPT_SEVERITY_INFO );
  111. BOARD* brd = LoadBoard( aStepJob->m_filename, true );
  112. brd->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
  113. if( aStepJob->m_outputFile.IsEmpty() )
  114. {
  115. wxFileName fn = brd->GetFileName();
  116. fn.SetName( fn.GetName() );
  117. switch( aStepJob->m_format )
  118. {
  119. case JOB_EXPORT_PCB_3D::FORMAT::VRML: fn.SetExt( FILEEXT::VrmlFileExtension );
  120. break;
  121. case JOB_EXPORT_PCB_3D::FORMAT::STEP: fn.SetExt( FILEEXT::StepFileExtension );
  122. break;
  123. case JOB_EXPORT_PCB_3D::FORMAT::BREP: fn.SetExt( FILEEXT::BrepFileExtension );
  124. break;
  125. case JOB_EXPORT_PCB_3D::FORMAT::XAO: fn.SetExt( FILEEXT::XaoFileExtension );
  126. break;
  127. case JOB_EXPORT_PCB_3D::FORMAT::GLB: fn.SetExt( FILEEXT::GltfBinaryFileExtension );
  128. break;
  129. default:
  130. return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
  131. }
  132. aStepJob->m_outputFile = fn.GetFullName();
  133. }
  134. if( aStepJob->m_format == JOB_EXPORT_PCB_3D::FORMAT::VRML )
  135. {
  136. double scale = 0.0;
  137. switch ( aStepJob->m_vrmlUnits )
  138. {
  139. case JOB_EXPORT_PCB_3D::VRML_UNITS::MILLIMETERS: scale = 1.0; break;
  140. case JOB_EXPORT_PCB_3D::VRML_UNITS::METERS: scale = 0.001; break;
  141. case JOB_EXPORT_PCB_3D::VRML_UNITS::TENTHS: scale = 10.0 / 25.4; break;
  142. case JOB_EXPORT_PCB_3D::VRML_UNITS::INCHES: scale = 1.0 / 25.4; break;
  143. }
  144. EXPORTER_VRML vrmlExporter( brd );
  145. wxString messages;
  146. double originX = pcbIUScale.IUTomm( aStepJob->m_xOrigin );
  147. double originY = pcbIUScale.IUTomm( aStepJob->m_yOrigin );
  148. if( !aStepJob->m_hasUserOrigin )
  149. {
  150. BOX2I bbox = brd->ComputeBoundingBox( true, false );
  151. originX = pcbIUScale.IUTomm( bbox.GetCenter().x );
  152. originY = pcbIUScale.IUTomm( bbox.GetCenter().y );
  153. }
  154. bool success = vrmlExporter.ExportVRML_File(
  155. brd->GetProject(), &messages, aStepJob->m_outputFile, scale,
  156. aStepJob->m_includeUnspecified, aStepJob->m_includeDNP,
  157. !aStepJob->m_vrmlModelDir.IsEmpty(), aStepJob->m_vrmlRelativePaths,
  158. aStepJob->m_vrmlModelDir, originX, originY );
  159. if ( success )
  160. {
  161. m_reporter->Report( wxString::Format( _( "Successfully exported VRML to %s" ),
  162. aStepJob->m_outputFile ),
  163. RPT_SEVERITY_INFO );
  164. }
  165. else
  166. {
  167. m_reporter->Report( _( "Error exporting VRML" ), RPT_SEVERITY_ERROR );
  168. return CLI::EXIT_CODES::ERR_UNKNOWN;
  169. }
  170. }
  171. else
  172. {
  173. EXPORTER_STEP_PARAMS params;
  174. params.m_exportBoardBody = aStepJob->m_exportBoardBody;
  175. params.m_exportComponents = aStepJob->m_exportComponents;
  176. params.m_exportTracksVias = aStepJob->m_exportTracks;
  177. params.m_exportZones = aStepJob->m_exportZones;
  178. params.m_exportInnerCopper = aStepJob->m_exportInnerCopper;
  179. params.m_fuseShapes = aStepJob->m_fuseShapes;
  180. params.m_includeUnspecified = aStepJob->m_includeUnspecified;
  181. params.m_includeDNP = aStepJob->m_includeDNP;
  182. params.m_BoardOutlinesChainingEpsilon = aStepJob->m_BoardOutlinesChainingEpsilon;
  183. params.m_overwrite = aStepJob->m_overwrite;
  184. params.m_substModels = aStepJob->m_substModels;
  185. params.m_origin = VECTOR2D( aStepJob->m_xOrigin, aStepJob->m_yOrigin );
  186. params.m_useDrillOrigin = aStepJob->m_useDrillOrigin;
  187. params.m_useGridOrigin = aStepJob->m_useGridOrigin;
  188. params.m_boardOnly = aStepJob->m_boardOnly;
  189. params.m_optimizeStep = aStepJob->m_optimizeStep;
  190. params.m_netFilter = aStepJob->m_netFilter;
  191. switch( aStepJob->m_format )
  192. {
  193. case JOB_EXPORT_PCB_3D::FORMAT::STEP:
  194. params.m_format = EXPORTER_STEP_PARAMS::FORMAT::STEP;
  195. break;
  196. case JOB_EXPORT_PCB_3D::FORMAT::BREP:
  197. params.m_format = EXPORTER_STEP_PARAMS::FORMAT::BREP;
  198. break;
  199. case JOB_EXPORT_PCB_3D::FORMAT::XAO:
  200. params.m_format = EXPORTER_STEP_PARAMS::FORMAT::XAO;
  201. break;
  202. case JOB_EXPORT_PCB_3D::FORMAT::GLB:
  203. params.m_format = EXPORTER_STEP_PARAMS::FORMAT::GLB;
  204. break;
  205. default:
  206. return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
  207. }
  208. EXPORTER_STEP stepExporter( brd, params );
  209. stepExporter.m_outputFile = aStepJob->m_outputFile;
  210. if( !stepExporter.Export() )
  211. return CLI::EXIT_CODES::ERR_UNKNOWN;
  212. }
  213. return CLI::EXIT_CODES::OK;
  214. }
  215. int PCBNEW_JOBS_HANDLER::JobExportRender( JOB* aJob )
  216. {
  217. JOB_PCB_RENDER* aRenderJob = dynamic_cast<JOB_PCB_RENDER*>( aJob );
  218. if( aRenderJob == nullptr )
  219. return CLI::EXIT_CODES::ERR_UNKNOWN;
  220. if( aJob->IsCli() )
  221. m_reporter->Report( _( "Loading board\n" ), RPT_SEVERITY_INFO );
  222. BOARD* brd = LoadBoard( aRenderJob->m_filename, true );
  223. brd->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
  224. BOARD_ADAPTER m_boardAdapter;
  225. m_boardAdapter.SetBoard( brd );
  226. m_boardAdapter.m_IsBoardView = false;
  227. m_boardAdapter.m_IsPreviewer =
  228. true; // Force display 3D models, regardless the 3D viewer options
  229. EDA_3D_VIEWER_SETTINGS* cfg =
  230. Pgm().GetSettingsManager().GetAppSettings<EDA_3D_VIEWER_SETTINGS>();
  231. if( aRenderJob->m_bgStyle == JOB_PCB_RENDER::BG_STYLE::TRANSPARENT
  232. || ( aRenderJob->m_bgStyle == JOB_PCB_RENDER::BG_STYLE::DEFAULT
  233. && aRenderJob->m_format == JOB_PCB_RENDER::FORMAT::PNG ) )
  234. {
  235. BOARD_ADAPTER::g_DefaultBackgroundTop = COLOR4D( 1.0, 1.0, 1.0, 0.0 );
  236. BOARD_ADAPTER::g_DefaultBackgroundBot = COLOR4D( 1.0, 1.0, 1.0, 0.0 );
  237. }
  238. if( aRenderJob->m_quality == JOB_PCB_RENDER::QUALITY::BASIC )
  239. {
  240. // Silkscreen is pixelated without antialiasing
  241. cfg->m_Render.raytrace_anti_aliasing = true;
  242. cfg->m_Render.raytrace_backfloor = false;
  243. cfg->m_Render.raytrace_post_processing = false;
  244. cfg->m_Render.raytrace_procedural_textures = false;
  245. cfg->m_Render.raytrace_reflections = false;
  246. cfg->m_Render.raytrace_shadows = false;
  247. // Better colors
  248. cfg->m_Render.differentiate_plated_copper = true;
  249. // Tracks below soldermask are not visible without refractions
  250. cfg->m_Render.raytrace_refractions = true;
  251. cfg->m_Render.raytrace_recursivelevel_refractions = 1;
  252. }
  253. else if( aRenderJob->m_quality == JOB_PCB_RENDER::QUALITY::HIGH )
  254. {
  255. cfg->m_Render.raytrace_anti_aliasing = true;
  256. cfg->m_Render.raytrace_backfloor = true;
  257. cfg->m_Render.raytrace_post_processing = true;
  258. cfg->m_Render.raytrace_procedural_textures = true;
  259. cfg->m_Render.raytrace_reflections = true;
  260. cfg->m_Render.raytrace_shadows = true;
  261. cfg->m_Render.raytrace_refractions = true;
  262. cfg->m_Render.differentiate_plated_copper = true;
  263. }
  264. if( aRenderJob->m_floor )
  265. {
  266. cfg->m_Render.raytrace_backfloor = true;
  267. cfg->m_Render.raytrace_shadows = true;
  268. cfg->m_Render.raytrace_post_processing = true;
  269. }
  270. cfg->m_CurrentPreset = aRenderJob->m_colorPreset;
  271. m_boardAdapter.m_Cfg = cfg;
  272. m_boardAdapter.Set3dCacheManager( PROJECT_PCB::Get3DCacheManager( brd->GetProject() ) );
  273. static std::map<JOB_PCB_RENDER::SIDE, VIEW3D_TYPE> s_viewCmdMap = {
  274. { JOB_PCB_RENDER::SIDE::TOP, VIEW3D_TYPE::VIEW3D_TOP },
  275. { JOB_PCB_RENDER::SIDE::BOTTOM, VIEW3D_TYPE::VIEW3D_BOTTOM },
  276. { JOB_PCB_RENDER::SIDE::LEFT, VIEW3D_TYPE::VIEW3D_LEFT },
  277. { JOB_PCB_RENDER::SIDE::RIGHT, VIEW3D_TYPE::VIEW3D_RIGHT },
  278. { JOB_PCB_RENDER::SIDE::FRONT, VIEW3D_TYPE::VIEW3D_FRONT },
  279. { JOB_PCB_RENDER::SIDE::BACK, VIEW3D_TYPE::VIEW3D_BACK },
  280. };
  281. PROJECTION_TYPE projection =
  282. aRenderJob->m_perspective ? PROJECTION_TYPE::PERSPECTIVE : PROJECTION_TYPE::ORTHO;
  283. wxSize windowSize( aRenderJob->m_width, aRenderJob->m_height );
  284. TRACK_BALL camera( 2 * RANGE_SCALE_3D );
  285. camera.SetProjection( projection );
  286. camera.SetCurWindowSize( windowSize );
  287. RENDER_3D_RAYTRACE_RAM raytrace( m_boardAdapter, camera );
  288. raytrace.SetCurWindowSize( windowSize );
  289. for( bool first = true; raytrace.Redraw( false, m_reporter, m_reporter ); first = false )
  290. {
  291. if( first )
  292. {
  293. const float cmTo3D = m_boardAdapter.BiuTo3dUnits() * pcbIUScale.mmToIU( 10.0 );
  294. // First redraw resets lookat point to the board center, so set up the camera here
  295. camera.ViewCommand_T1( s_viewCmdMap[aRenderJob->m_side] );
  296. camera.SetLookAtPos_T1(
  297. camera.GetLookAtPos_T1()
  298. + SFVEC3F( aRenderJob->m_pivot.x, aRenderJob->m_pivot.y, aRenderJob->m_pivot.z )
  299. * cmTo3D );
  300. camera.Pan_T1(
  301. SFVEC3F( aRenderJob->m_pan.x, aRenderJob->m_pan.y, aRenderJob->m_pan.z ) );
  302. camera.Zoom_T1( aRenderJob->m_zoom );
  303. camera.RotateX_T1( DEG2RAD( aRenderJob->m_rotation.x ) );
  304. camera.RotateY_T1( DEG2RAD( aRenderJob->m_rotation.y ) );
  305. camera.RotateZ_T1( DEG2RAD( aRenderJob->m_rotation.z ) );
  306. camera.Interpolate( 1.0f );
  307. camera.SetT0_and_T1_current_T();
  308. camera.ParametersChanged();
  309. }
  310. }
  311. GLubyte* rgbaBuffer = raytrace.GetBuffer();
  312. wxSize realSize = raytrace.GetRealBufferSize();
  313. bool success = !!rgbaBuffer;
  314. if( rgbaBuffer )
  315. {
  316. const unsigned int wxh = realSize.x * realSize.y;
  317. unsigned char* rgbBuffer = (unsigned char*) malloc( wxh * 3 );
  318. unsigned char* alphaBuffer = (unsigned char*) malloc( wxh );
  319. unsigned char* rgbaPtr = rgbaBuffer;
  320. unsigned char* rgbPtr = rgbBuffer;
  321. unsigned char* alphaPtr = alphaBuffer;
  322. for( int y = 0; y < realSize.y; y++ )
  323. {
  324. for( int x = 0; x < realSize.x; x++ )
  325. {
  326. rgbPtr[0] = rgbaPtr[0];
  327. rgbPtr[1] = rgbaPtr[1];
  328. rgbPtr[2] = rgbaPtr[2];
  329. alphaPtr[0] = rgbaPtr[3];
  330. rgbaPtr += 4;
  331. rgbPtr += 3;
  332. alphaPtr += 1;
  333. }
  334. }
  335. wxImage image( realSize );
  336. image.SetData( rgbBuffer );
  337. image.SetAlpha( alphaBuffer );
  338. image = image.Mirror( false );
  339. image.SetOption( wxIMAGE_OPTION_QUALITY, 90 );
  340. image.SaveFile( aRenderJob->m_outputFile,
  341. aRenderJob->m_format == JOB_PCB_RENDER::FORMAT::PNG ? wxBITMAP_TYPE_PNG
  342. : wxBITMAP_TYPE_JPEG );
  343. }
  344. m_reporter->Report( wxString::Format( _( "Actual image size: %dx%d" ), realSize.x, realSize.y )
  345. + wxS( "\n" ),
  346. RPT_SEVERITY_INFO );
  347. if( success )
  348. m_reporter->Report( _( "Successfully created 3D render image" ) + wxS( "\n" ),
  349. RPT_SEVERITY_INFO );
  350. else
  351. m_reporter->Report( _( "Error creating 3D render image" ) + wxS( "\n" ),
  352. RPT_SEVERITY_ERROR );
  353. return CLI::EXIT_CODES::OK;
  354. }
  355. int PCBNEW_JOBS_HANDLER::JobExportSvg( JOB* aJob )
  356. {
  357. JOB_EXPORT_PCB_SVG* aSvgJob = dynamic_cast<JOB_EXPORT_PCB_SVG*>( aJob );
  358. if( aSvgJob == nullptr )
  359. return CLI::EXIT_CODES::ERR_UNKNOWN;
  360. PCB_PLOT_SVG_OPTIONS svgPlotOptions;
  361. svgPlotOptions.m_blackAndWhite = aSvgJob->m_blackAndWhite;
  362. svgPlotOptions.m_colorTheme = aSvgJob->m_colorTheme;
  363. svgPlotOptions.m_outputFile = aSvgJob->m_outputFile;
  364. svgPlotOptions.m_mirror = aSvgJob->m_mirror;
  365. svgPlotOptions.m_negative = aSvgJob->m_negative;
  366. svgPlotOptions.m_pageSizeMode = aSvgJob->m_pageSizeMode;
  367. svgPlotOptions.m_printMaskLayer = aSvgJob->m_printMaskLayer;
  368. svgPlotOptions.m_plotFrame = aSvgJob->m_plotDrawingSheet;
  369. svgPlotOptions.m_drillShapeOption = aSvgJob->m_drillShapeOption;
  370. if( aJob->IsCli() )
  371. m_reporter->Report( _( "Loading board\n" ), RPT_SEVERITY_INFO );
  372. BOARD* brd = LoadBoard( aSvgJob->m_filename, true );
  373. loadOverrideDrawingSheet( brd, aSvgJob->m_drawingSheet );
  374. brd->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
  375. if( aJob->IsCli() )
  376. {
  377. if( EXPORT_SVG::Plot( brd, svgPlotOptions ) )
  378. m_reporter->Report( _( "Successfully created svg file" ) + wxS( "\n" ), RPT_SEVERITY_INFO );
  379. else
  380. m_reporter->Report( _( "Error creating svg file" ) + wxS( "\n" ), RPT_SEVERITY_ERROR );
  381. }
  382. return CLI::EXIT_CODES::OK;
  383. }
  384. int PCBNEW_JOBS_HANDLER::JobExportDxf( JOB* aJob )
  385. {
  386. JOB_EXPORT_PCB_DXF* aDxfJob = dynamic_cast<JOB_EXPORT_PCB_DXF*>( aJob );
  387. if( aDxfJob == nullptr )
  388. return CLI::EXIT_CODES::ERR_UNKNOWN;
  389. if( aJob->IsCli() )
  390. m_reporter->Report( _( "Loading board\n" ), RPT_SEVERITY_INFO );
  391. BOARD* brd = LoadBoard( aDxfJob->m_filename, true );
  392. loadOverrideDrawingSheet( brd, aDxfJob->m_drawingSheet );
  393. brd->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
  394. if( aDxfJob->m_outputFile.IsEmpty() )
  395. {
  396. wxFileName fn = brd->GetFileName();
  397. fn.SetName( fn.GetName() );
  398. fn.SetExt( GetDefaultPlotExtension( PLOT_FORMAT::DXF ) );
  399. aDxfJob->m_outputFile = fn.GetFullName();
  400. }
  401. PCB_PLOT_PARAMS plotOpts;
  402. plotOpts.SetFormat( PLOT_FORMAT::DXF );
  403. plotOpts.SetDXFPlotPolygonMode( aDxfJob->m_plotGraphicItemsUsingContours );
  404. if( aDxfJob->m_dxfUnits == JOB_EXPORT_PCB_DXF::DXF_UNITS::MILLIMETERS )
  405. plotOpts.SetDXFPlotUnits( DXF_UNITS::MILLIMETERS );
  406. else
  407. plotOpts.SetDXFPlotUnits( DXF_UNITS::INCHES );
  408. plotOpts.SetPlotFrameRef( aDxfJob->m_plotBorderTitleBlocks );
  409. plotOpts.SetPlotValue( aDxfJob->m_plotFootprintValues );
  410. plotOpts.SetPlotReference( aDxfJob->m_plotRefDes );
  411. plotOpts.SetLayerSelection( aDxfJob->m_printMaskLayer );
  412. DXF_PLOTTER* plotter = (DXF_PLOTTER*) StartPlotBoard(
  413. brd, &plotOpts, UNDEFINED_LAYER, aDxfJob->m_outputFile, wxEmptyString, wxEmptyString );
  414. if( plotter )
  415. {
  416. PlotBoardLayers( brd, plotter, aDxfJob->m_printMaskLayer, plotOpts );
  417. plotter->EndPlot();
  418. }
  419. delete plotter;
  420. return CLI::EXIT_CODES::OK;
  421. }
  422. int PCBNEW_JOBS_HANDLER::JobExportPdf( JOB* aJob )
  423. {
  424. JOB_EXPORT_PCB_PDF* aPdfJob = dynamic_cast<JOB_EXPORT_PCB_PDF*>( aJob );
  425. if( aPdfJob == nullptr )
  426. return CLI::EXIT_CODES::ERR_UNKNOWN;
  427. if( aJob->IsCli() )
  428. m_reporter->Report( _( "Loading board\n" ), RPT_SEVERITY_INFO );
  429. BOARD* brd = LoadBoard( aPdfJob->m_filename, true );
  430. loadOverrideDrawingSheet( brd, aPdfJob->m_drawingSheet );
  431. brd->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
  432. if( aPdfJob->m_outputFile.IsEmpty() )
  433. {
  434. wxFileName fn = brd->GetFileName();
  435. fn.SetName( fn.GetName() );
  436. fn.SetExt( GetDefaultPlotExtension( PLOT_FORMAT::PDF ) );
  437. aPdfJob->m_outputFile = fn.GetFullName();
  438. }
  439. PCB_PLOT_PARAMS plotOpts;
  440. plotOpts.SetFormat( PLOT_FORMAT::PDF );
  441. plotOpts.SetPlotFrameRef( aPdfJob->m_plotBorderTitleBlocks );
  442. plotOpts.SetPlotValue( aPdfJob->m_plotFootprintValues );
  443. plotOpts.SetPlotReference( aPdfJob->m_plotRefDes );
  444. plotOpts.SetLayerSelection( aPdfJob->m_printMaskLayer );
  445. SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
  446. plotOpts.SetColorSettings( mgr.GetColorSettings( aPdfJob->m_colorTheme ) );
  447. plotOpts.SetMirror( aPdfJob->m_mirror );
  448. plotOpts.SetBlackAndWhite( aPdfJob->m_blackAndWhite );
  449. plotOpts.SetNegative( aPdfJob->m_negative );
  450. switch( aPdfJob->m_drillShapeOption )
  451. {
  452. default:
  453. case 0: plotOpts.SetDrillMarksType( DRILL_MARKS::NO_DRILL_SHAPE ); break;
  454. case 1: plotOpts.SetDrillMarksType( DRILL_MARKS::SMALL_DRILL_SHAPE ); break;
  455. case 2: plotOpts.SetDrillMarksType( DRILL_MARKS::FULL_DRILL_SHAPE ); break;
  456. }
  457. PDF_PLOTTER* plotter = (PDF_PLOTTER*) StartPlotBoard(
  458. brd, &plotOpts, UNDEFINED_LAYER, aPdfJob->m_outputFile, wxEmptyString, wxEmptyString );
  459. if( plotter )
  460. {
  461. PlotBoardLayers( brd, plotter, aPdfJob->m_printMaskLayer, plotOpts );
  462. PlotInteractiveLayer( brd, plotter, plotOpts );
  463. plotter->EndPlot();
  464. }
  465. delete plotter;
  466. return CLI::EXIT_CODES::OK;
  467. }
  468. int PCBNEW_JOBS_HANDLER::JobExportGerbers( JOB* aJob )
  469. {
  470. int exitCode = CLI::EXIT_CODES::OK;
  471. JOB_EXPORT_PCB_GERBERS* aGerberJob = dynamic_cast<JOB_EXPORT_PCB_GERBERS*>( aJob );
  472. if( aGerberJob == nullptr )
  473. return CLI::EXIT_CODES::ERR_UNKNOWN;
  474. if( aJob->IsCli() )
  475. m_reporter->Report( _( "Loading board\n" ), RPT_SEVERITY_INFO );
  476. BOARD* brd = LoadBoard( aGerberJob->m_filename, true );
  477. loadOverrideDrawingSheet( brd, aGerberJob->m_drawingSheet );
  478. brd->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
  479. PCB_PLOT_PARAMS boardPlotOptions = brd->GetPlotOptions();
  480. LSET plotOnAllLayersSelection = boardPlotOptions.GetPlotOnAllLayersSelection();
  481. GERBER_JOBFILE_WRITER jobfile_writer( brd );
  482. wxString fileExt;
  483. if( aGerberJob->m_useBoardPlotParams )
  484. {
  485. // The board plot options are saved with all copper layers enabled, even those that don't
  486. // exist in the current stackup. This is done so the layers are automatically enabled in the plot
  487. // dialog when the user enables them. We need to filter out these not-enabled layers here so
  488. // we don't plot 32 layers when we only have 4, etc.
  489. LSET plotLayers = ( boardPlotOptions.GetLayerSelection() & LSET::AllNonCuMask() )
  490. | ( brd->GetEnabledLayers() & LSET::AllCuMask() );
  491. aGerberJob->m_printMaskLayer = plotLayers.SeqStackupForPlotting();
  492. aGerberJob->m_layersIncludeOnAll = boardPlotOptions.GetPlotOnAllLayersSelection();
  493. }
  494. else
  495. {
  496. // default to the board enabled layers
  497. if( aGerberJob->m_printMaskLayer == 0 )
  498. aGerberJob->m_printMaskLayer = brd->GetEnabledLayers().SeqStackupForPlotting();
  499. if( aGerberJob->m_layersIncludeOnAllSet )
  500. aGerberJob->m_layersIncludeOnAll = plotOnAllLayersSelection;
  501. }
  502. for( LSEQ seq = LSET( aGerberJob->m_printMaskLayer ).UIOrder(); seq; ++seq )
  503. {
  504. LSEQ plotSequence;
  505. // Base layer always gets plotted first.
  506. plotSequence.push_back( *seq );
  507. // Now all the "include on all" layers
  508. for( LSEQ seqAll = aGerberJob->m_layersIncludeOnAll.UIOrder(); seqAll; ++seqAll )
  509. {
  510. // Don't plot the same layer more than once;
  511. if( find( plotSequence.begin(), plotSequence.end(), *seqAll ) != plotSequence.end() )
  512. continue;
  513. plotSequence.push_back( *seqAll );
  514. }
  515. // Pick the basename from the board file
  516. wxFileName fn( brd->GetFileName() );
  517. PCB_LAYER_ID layer = *seq;
  518. PCB_PLOT_PARAMS plotOpts;
  519. if( aGerberJob->m_useBoardPlotParams )
  520. plotOpts = boardPlotOptions;
  521. else
  522. populateGerberPlotOptionsFromJob( plotOpts, aGerberJob );
  523. if( plotOpts.GetUseGerberProtelExtensions() )
  524. fileExt = GetGerberProtelExtension( layer );
  525. else
  526. fileExt = FILEEXT::GerberFileExtension;
  527. BuildPlotFileName( &fn, aGerberJob->m_outputFile, brd->GetLayerName( layer ), fileExt );
  528. wxString fullname = fn.GetFullName();
  529. jobfile_writer.AddGbrFile( layer, fullname );
  530. // We are feeding it one layer at the start here to silence a logic check
  531. GERBER_PLOTTER* plotter = (GERBER_PLOTTER*) StartPlotBoard(
  532. brd, &plotOpts, layer, fn.GetFullPath(), wxEmptyString, wxEmptyString );
  533. if( plotter )
  534. {
  535. m_reporter->Report( wxString::Format( _( "Plotted to '%s'.\n" ), fn.GetFullPath() ),
  536. RPT_SEVERITY_ACTION );
  537. PlotBoardLayers( brd, plotter, plotSequence, plotOpts );
  538. plotter->EndPlot();
  539. }
  540. else
  541. {
  542. m_reporter->Report( wxString::Format( _( "Failed to plot to '%s'.\n" ),
  543. fn.GetFullPath() ),
  544. RPT_SEVERITY_ERROR );
  545. exitCode = CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  546. }
  547. delete plotter;
  548. }
  549. wxFileName fn( aGerberJob->m_filename );
  550. // Build gerber job file from basename
  551. BuildPlotFileName( &fn, aGerberJob->m_outputFile, wxT( "job" ),
  552. FILEEXT::GerberJobFileExtension );
  553. jobfile_writer.CreateJobFile( fn.GetFullPath() );
  554. return exitCode;
  555. }
  556. void PCBNEW_JOBS_HANDLER::populateGerberPlotOptionsFromJob( PCB_PLOT_PARAMS& aPlotOpts,
  557. JOB_EXPORT_PCB_GERBER* aJob )
  558. {
  559. aPlotOpts.SetFormat( PLOT_FORMAT::GERBER );
  560. aPlotOpts.SetPlotFrameRef( aJob->m_plotBorderTitleBlocks );
  561. aPlotOpts.SetPlotValue( aJob->m_plotFootprintValues );
  562. aPlotOpts.SetPlotReference( aJob->m_plotRefDes );
  563. aPlotOpts.SetSubtractMaskFromSilk( aJob->m_subtractSolderMaskFromSilk );
  564. // Always disable plot pad holes
  565. aPlotOpts.SetDrillMarksType( DRILL_MARKS::NO_DRILL_SHAPE );
  566. aPlotOpts.SetDisableGerberMacros( aJob->m_disableApertureMacros );
  567. aPlotOpts.SetUseGerberX2format( aJob->m_useX2Format );
  568. aPlotOpts.SetIncludeGerberNetlistInfo( aJob->m_includeNetlistAttributes );
  569. aPlotOpts.SetUseAuxOrigin( aJob->m_useAuxOrigin );
  570. aPlotOpts.SetUseGerberProtelExtensions( aJob->m_useProtelFileExtension );
  571. aPlotOpts.SetGerberPrecision( aJob->m_precision );
  572. }
  573. int PCBNEW_JOBS_HANDLER::JobExportGerber( JOB* aJob )
  574. {
  575. int exitCode = CLI::EXIT_CODES::OK;
  576. JOB_EXPORT_PCB_GERBER* aGerberJob = dynamic_cast<JOB_EXPORT_PCB_GERBER*>( aJob );
  577. if( aGerberJob == nullptr )
  578. return CLI::EXIT_CODES::ERR_UNKNOWN;
  579. if( aJob->IsCli() )
  580. m_reporter->Report( _( "Loading board\n" ), RPT_SEVERITY_INFO );
  581. BOARD* brd = LoadBoard( aGerberJob->m_filename, true );
  582. brd->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
  583. if( aGerberJob->m_outputFile.IsEmpty() )
  584. {
  585. wxFileName fn = brd->GetFileName();
  586. fn.SetName( fn.GetName() );
  587. fn.SetExt( GetDefaultPlotExtension( PLOT_FORMAT::GERBER ) );
  588. aGerberJob->m_outputFile = fn.GetFullName();
  589. }
  590. PCB_PLOT_PARAMS plotOpts;
  591. populateGerberPlotOptionsFromJob( plotOpts, aGerberJob );
  592. plotOpts.SetLayerSelection( aGerberJob->m_printMaskLayer );
  593. // We are feeding it one layer at the start here to silence a logic check
  594. GERBER_PLOTTER* plotter = (GERBER_PLOTTER*) StartPlotBoard(
  595. brd, &plotOpts, aGerberJob->m_printMaskLayer.front(), aGerberJob->m_outputFile,
  596. wxEmptyString, wxEmptyString );
  597. if( plotter )
  598. {
  599. PlotBoardLayers( brd, plotter, aGerberJob->m_printMaskLayer, plotOpts );
  600. plotter->EndPlot();
  601. }
  602. else
  603. {
  604. m_reporter->Report( wxString::Format( _( "Failed to plot to '%s'.\n" ),
  605. aGerberJob->m_outputFile ),
  606. RPT_SEVERITY_ERROR );
  607. exitCode = CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  608. }
  609. delete plotter;
  610. return exitCode;
  611. }
  612. static DRILL_PRECISION precisionListForInches( 2, 4 );
  613. static DRILL_PRECISION precisionListForMetric( 3, 3 );
  614. int PCBNEW_JOBS_HANDLER::JobExportDrill( JOB* aJob )
  615. {
  616. JOB_EXPORT_PCB_DRILL* aDrillJob = dynamic_cast<JOB_EXPORT_PCB_DRILL*>( aJob );
  617. if( aDrillJob == nullptr )
  618. return CLI::EXIT_CODES::ERR_UNKNOWN;
  619. if( aJob->IsCli() )
  620. m_reporter->Report( _( "Loading board\n" ), RPT_SEVERITY_INFO );
  621. BOARD* brd = LoadBoard( aDrillJob->m_filename, true );
  622. // ensure output dir exists
  623. wxFileName fn( aDrillJob->m_outputDir + wxT( "/" ) );
  624. if( !fn.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
  625. {
  626. m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
  627. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  628. }
  629. std::unique_ptr<GENDRILL_WRITER_BASE> drillWriter;
  630. if( aDrillJob->m_format == JOB_EXPORT_PCB_DRILL::DRILL_FORMAT::EXCELLON )
  631. drillWriter = std::make_unique<EXCELLON_WRITER>( brd );
  632. else
  633. drillWriter = std::make_unique<GERBER_WRITER>( brd );
  634. VECTOR2I offset;
  635. if( aDrillJob->m_drillOrigin == JOB_EXPORT_PCB_DRILL::DRILL_ORIGIN::ABS )
  636. offset = VECTOR2I( 0, 0 );
  637. else
  638. offset = brd->GetDesignSettings().GetAuxOrigin();
  639. PLOT_FORMAT mapFormat = PLOT_FORMAT::PDF;
  640. switch( aDrillJob->m_mapFormat )
  641. {
  642. case JOB_EXPORT_PCB_DRILL::MAP_FORMAT::POSTSCRIPT: mapFormat = PLOT_FORMAT::POST; break;
  643. case JOB_EXPORT_PCB_DRILL::MAP_FORMAT::GERBER_X2: mapFormat = PLOT_FORMAT::GERBER; break;
  644. case JOB_EXPORT_PCB_DRILL::MAP_FORMAT::DXF: mapFormat = PLOT_FORMAT::DXF; break;
  645. case JOB_EXPORT_PCB_DRILL::MAP_FORMAT::SVG: mapFormat = PLOT_FORMAT::SVG; break;
  646. default:
  647. case JOB_EXPORT_PCB_DRILL::MAP_FORMAT::PDF: mapFormat = PLOT_FORMAT::PDF; break;
  648. }
  649. if( aDrillJob->m_format == JOB_EXPORT_PCB_DRILL::DRILL_FORMAT::EXCELLON )
  650. {
  651. EXCELLON_WRITER::ZEROS_FMT zeroFmt;
  652. switch( aDrillJob->m_zeroFormat )
  653. {
  654. case JOB_EXPORT_PCB_DRILL::ZEROS_FORMAT::KEEP_ZEROS:
  655. zeroFmt = EXCELLON_WRITER::KEEP_ZEROS;
  656. break;
  657. case JOB_EXPORT_PCB_DRILL::ZEROS_FORMAT::SUPPRESS_LEADING:
  658. zeroFmt = EXCELLON_WRITER::SUPPRESS_LEADING;
  659. break;
  660. case JOB_EXPORT_PCB_DRILL::ZEROS_FORMAT::SUPPRESS_TRAILING:
  661. zeroFmt = EXCELLON_WRITER::SUPPRESS_TRAILING;
  662. break;
  663. case JOB_EXPORT_PCB_DRILL::ZEROS_FORMAT::DECIMAL:
  664. default:
  665. zeroFmt = EXCELLON_WRITER::DECIMAL_FORMAT;
  666. break;
  667. }
  668. DRILL_PRECISION precision;
  669. if( aDrillJob->m_drillUnits == JOB_EXPORT_PCB_DRILL::DRILL_UNITS::INCHES )
  670. precision = precisionListForInches;
  671. else
  672. precision = precisionListForMetric;
  673. EXCELLON_WRITER* excellonWriter = dynamic_cast<EXCELLON_WRITER*>( drillWriter.get() );
  674. if( excellonWriter == nullptr )
  675. return CLI::EXIT_CODES::ERR_UNKNOWN;
  676. excellonWriter->SetFormat( aDrillJob->m_drillUnits
  677. == JOB_EXPORT_PCB_DRILL::DRILL_UNITS::MILLIMETERS,
  678. zeroFmt, precision.m_Lhs, precision.m_Rhs );
  679. excellonWriter->SetOptions( aDrillJob->m_excellonMirrorY,
  680. aDrillJob->m_excellonMinimalHeader,
  681. offset, aDrillJob->m_excellonCombinePTHNPTH );
  682. excellonWriter->SetRouteModeForOvalHoles( aDrillJob->m_excellonOvalDrillRoute );
  683. excellonWriter->SetMapFileFormat( mapFormat );
  684. if( !excellonWriter->CreateDrillandMapFilesSet( aDrillJob->m_outputDir, true,
  685. aDrillJob->m_generateMap, m_reporter ) )
  686. {
  687. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  688. }
  689. }
  690. else if( aDrillJob->m_format == JOB_EXPORT_PCB_DRILL::DRILL_FORMAT::GERBER )
  691. {
  692. GERBER_WRITER* gerberWriter = dynamic_cast<GERBER_WRITER*>( drillWriter.get() );
  693. if( gerberWriter == nullptr )
  694. return CLI::EXIT_CODES::ERR_UNKNOWN;
  695. // Set gerber precision: only 5 or 6 digits for mantissa are allowed
  696. // (SetFormat() accept 5 or 6, and any other value set the precision to 5)
  697. // the integer part precision is always 4, and units always mm
  698. gerberWriter->SetFormat( aDrillJob->m_gerberPrecision );
  699. gerberWriter->SetOptions( offset );
  700. gerberWriter->SetMapFileFormat( mapFormat );
  701. if( !gerberWriter->CreateDrillandMapFilesSet( aDrillJob->m_outputDir, true,
  702. aDrillJob->m_generateMap, m_reporter ) )
  703. {
  704. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  705. }
  706. }
  707. return CLI::EXIT_CODES::OK;
  708. }
  709. int PCBNEW_JOBS_HANDLER::JobExportPos( JOB* aJob )
  710. {
  711. JOB_EXPORT_PCB_POS* aPosJob = dynamic_cast<JOB_EXPORT_PCB_POS*>( aJob );
  712. if( aPosJob == nullptr )
  713. return CLI::EXIT_CODES::ERR_UNKNOWN;
  714. if( aJob->IsCli() )
  715. m_reporter->Report( _( "Loading board\n" ), RPT_SEVERITY_INFO );
  716. BOARD* brd = LoadBoard( aPosJob->m_filename, true );
  717. if( aPosJob->m_outputFile.IsEmpty() )
  718. {
  719. wxFileName fn = brd->GetFileName();
  720. fn.SetName( fn.GetName() );
  721. if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::ASCII )
  722. fn.SetExt( FILEEXT::FootprintPlaceFileExtension );
  723. else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV )
  724. fn.SetExt( FILEEXT::CsvFileExtension );
  725. else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::GERBER )
  726. fn.SetExt( FILEEXT::GerberFileExtension );
  727. aPosJob->m_outputFile = fn.GetFullName();
  728. }
  729. if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::ASCII
  730. || aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV )
  731. {
  732. FILE* file = nullptr;
  733. file = wxFopen( aPosJob->m_outputFile, wxS( "wt" ) );
  734. if( file == nullptr )
  735. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  736. std::string data;
  737. bool frontSide = aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::FRONT
  738. || aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH;
  739. bool backSide = aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BACK
  740. || aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH;
  741. PLACE_FILE_EXPORTER exporter( brd,
  742. aPosJob->m_units == JOB_EXPORT_PCB_POS::UNITS::MILLIMETERS,
  743. aPosJob->m_smdOnly, aPosJob->m_excludeFootprintsWithTh,
  744. aPosJob->m_excludeDNP,
  745. frontSide, backSide,
  746. aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV,
  747. aPosJob->m_useDrillPlaceFileOrigin,
  748. aPosJob->m_negateBottomX );
  749. data = exporter.GenPositionData();
  750. fputs( data.c_str(), file );
  751. fclose( file );
  752. }
  753. else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::GERBER )
  754. {
  755. PLACEFILE_GERBER_WRITER exporter( brd );
  756. PCB_LAYER_ID gbrLayer = F_Cu;
  757. if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BACK )
  758. gbrLayer = B_Cu;
  759. exporter.CreatePlaceFile( aPosJob->m_outputFile, gbrLayer, aPosJob->m_gerberBoardEdge );
  760. }
  761. return CLI::EXIT_CODES::OK;
  762. }
  763. extern FOOTPRINT* try_load_footprint( const wxFileName& aFileName, PCB_IO_MGR::PCB_FILE_T aFileType,
  764. const wxString& aName );
  765. int PCBNEW_JOBS_HANDLER::JobExportFpUpgrade( JOB* aJob )
  766. {
  767. JOB_FP_UPGRADE* upgradeJob = dynamic_cast<JOB_FP_UPGRADE*>( aJob );
  768. if( upgradeJob == nullptr )
  769. return CLI::EXIT_CODES::ERR_UNKNOWN;
  770. PCB_IO_MGR::PCB_FILE_T fileType = PCB_IO_MGR::GuessPluginTypeFromLibPath( upgradeJob->m_libraryPath );
  771. if( !upgradeJob->m_outputLibraryPath.IsEmpty() )
  772. {
  773. if( wxFile::Exists( upgradeJob->m_outputLibraryPath ) ||
  774. wxDir::Exists( upgradeJob->m_outputLibraryPath) )
  775. {
  776. m_reporter->Report( _( "Output path must not conflict with existing path\n" ),
  777. RPT_SEVERITY_ERROR );
  778. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  779. }
  780. }
  781. else if( fileType != PCB_IO_MGR::KICAD_SEXP )
  782. {
  783. m_reporter->Report( _( "Output path must be specified to convert legacy and non-KiCad libraries\n" ),
  784. RPT_SEVERITY_ERROR );
  785. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  786. }
  787. if( fileType == PCB_IO_MGR::KICAD_SEXP )
  788. {
  789. if( !wxDir::Exists( upgradeJob->m_libraryPath ) )
  790. {
  791. m_reporter->Report( _( "Footprint library path does not exist or is not accessible\n" ),
  792. RPT_SEVERITY_INFO );
  793. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  794. }
  795. if( aJob->IsCli() )
  796. m_reporter->Report( _( "Loading footprint library\n" ), RPT_SEVERITY_INFO );
  797. PCB_IO_KICAD_SEXPR pcb_io( CTL_FOR_LIBRARY );
  798. FP_CACHE fpLib( &pcb_io, upgradeJob->m_libraryPath );
  799. try
  800. {
  801. fpLib.Load();
  802. }
  803. catch( ... )
  804. {
  805. m_reporter->Report( _( "Unable to load library\n" ), RPT_SEVERITY_ERROR );
  806. return CLI::EXIT_CODES::ERR_UNKNOWN;
  807. }
  808. bool shouldSave = upgradeJob->m_force;
  809. for( const auto& footprint : fpLib.GetFootprints() )
  810. {
  811. if( footprint.second->GetFootprint()->GetFileFormatVersionAtLoad()
  812. < SEXPR_BOARD_FILE_VERSION )
  813. {
  814. shouldSave = true;
  815. }
  816. }
  817. if( shouldSave )
  818. {
  819. m_reporter->Report( _( "Saving footprint library\n" ), RPT_SEVERITY_INFO );
  820. try
  821. {
  822. if( !upgradeJob->m_outputLibraryPath.IsEmpty() )
  823. {
  824. fpLib.SetPath( upgradeJob->m_outputLibraryPath );
  825. }
  826. fpLib.Save();
  827. }
  828. catch( ... )
  829. {
  830. m_reporter->Report( _( "Unable to save library\n" ), RPT_SEVERITY_ERROR );
  831. return CLI::EXIT_CODES::ERR_UNKNOWN;
  832. }
  833. }
  834. else
  835. {
  836. m_reporter->Report( _( "Footprint library was not updated\n" ), RPT_SEVERITY_INFO );
  837. }
  838. }
  839. else
  840. {
  841. if( !PCB_IO_MGR::ConvertLibrary( nullptr, upgradeJob->m_libraryPath, upgradeJob->m_outputLibraryPath ) )
  842. {
  843. m_reporter->Report( ( "Unable to convert library\n" ), RPT_SEVERITY_ERROR );
  844. return CLI::EXIT_CODES::ERR_UNKNOWN;
  845. }
  846. }
  847. return CLI::EXIT_CODES::OK;
  848. }
  849. int PCBNEW_JOBS_HANDLER::JobExportFpSvg( JOB* aJob )
  850. {
  851. JOB_FP_EXPORT_SVG* svgJob = dynamic_cast<JOB_FP_EXPORT_SVG*>( aJob );
  852. if( svgJob == nullptr )
  853. return CLI::EXIT_CODES::ERR_UNKNOWN;
  854. if( aJob->IsCli() )
  855. m_reporter->Report( _( "Loading footprint library\n" ), RPT_SEVERITY_INFO );
  856. PCB_IO_KICAD_SEXPR pcb_io( CTL_FOR_LIBRARY );
  857. FP_CACHE fpLib( &pcb_io, svgJob->m_libraryPath );
  858. try
  859. {
  860. fpLib.Load();
  861. }
  862. catch( ... )
  863. {
  864. m_reporter->Report( _( "Unable to load library\n" ), RPT_SEVERITY_ERROR );
  865. return CLI::EXIT_CODES::ERR_UNKNOWN;
  866. }
  867. if( !svgJob->m_outputDirectory.IsEmpty() && !wxDir::Exists( svgJob->m_outputDirectory ) )
  868. {
  869. wxFileName::Mkdir( svgJob->m_outputDirectory );
  870. }
  871. int exitCode = CLI::EXIT_CODES::OK;
  872. // Just plot all the symbols we can
  873. FP_CACHE_FOOTPRINT_MAP& footprintMap = fpLib.GetFootprints();
  874. bool singleFpPlotted = false;
  875. for( FP_CACHE_FOOTPRINT_MAP::iterator it = footprintMap.begin(); it != footprintMap.end();
  876. ++it )
  877. {
  878. const FOOTPRINT* fp = it->second->GetFootprint();
  879. if( !svgJob->m_footprint.IsEmpty() )
  880. {
  881. if( fp->GetFPID().GetLibItemName().wx_str() != svgJob->m_footprint )
  882. {
  883. // skip until we find the right footprint
  884. continue;
  885. }
  886. else
  887. {
  888. singleFpPlotted = true;
  889. }
  890. }
  891. exitCode = doFpExportSvg( svgJob, fp );
  892. if( exitCode != CLI::EXIT_CODES::OK )
  893. break;
  894. }
  895. if( !svgJob->m_footprint.IsEmpty() && !singleFpPlotted )
  896. {
  897. m_reporter->Report( _( "The given footprint could not be found to export." ) + wxS( "\n" ),
  898. RPT_SEVERITY_ERROR );
  899. }
  900. return CLI::EXIT_CODES::OK;
  901. }
  902. int PCBNEW_JOBS_HANDLER::doFpExportSvg( JOB_FP_EXPORT_SVG* aSvgJob, const FOOTPRINT* aFootprint )
  903. {
  904. // the hack for now is we create fake boards containing the footprint and plot the board
  905. // until we refactor better plot api later
  906. std::unique_ptr<BOARD> brd;
  907. brd.reset( CreateEmptyBoard() );
  908. brd->GetProject()->ApplyTextVars( aSvgJob->GetVarOverrides() );
  909. FOOTPRINT* fp = dynamic_cast<FOOTPRINT*>( aFootprint->Clone() );
  910. if( fp == nullptr )
  911. return CLI::EXIT_CODES::ERR_UNKNOWN;
  912. fp->SetLink( niluuid );
  913. fp->SetFlags( IS_NEW );
  914. fp->SetParent( brd.get() );
  915. for( PAD* pad : fp->Pads() )
  916. {
  917. pad->SetLocalRatsnestVisible( false );
  918. pad->SetNetCode( 0 );
  919. }
  920. fp->SetOrientation( ANGLE_0 );
  921. fp->SetPosition( VECTOR2I( 0, 0 ) );
  922. brd->Add( fp, ADD_MODE::INSERT, true );
  923. wxFileName outputFile;
  924. outputFile.SetPath( aSvgJob->m_outputDirectory );
  925. outputFile.SetName( aFootprint->GetFPID().GetLibItemName().wx_str() );
  926. outputFile.SetExt( FILEEXT::SVGFileExtension );
  927. m_reporter->Report( wxString::Format( _( "Plotting footprint '%s' to '%s'\n" ),
  928. aFootprint->GetFPID().GetLibItemName().wx_str(),
  929. outputFile.GetFullPath() ),
  930. RPT_SEVERITY_ACTION );
  931. PCB_PLOT_SVG_OPTIONS svgPlotOptions;
  932. svgPlotOptions.m_blackAndWhite = aSvgJob->m_blackAndWhite;
  933. svgPlotOptions.m_colorTheme = aSvgJob->m_colorTheme;
  934. svgPlotOptions.m_outputFile = outputFile.GetFullPath();
  935. svgPlotOptions.m_mirror = false;
  936. svgPlotOptions.m_pageSizeMode = 2; // board bounding box
  937. svgPlotOptions.m_printMaskLayer = aSvgJob->m_printMaskLayer;
  938. svgPlotOptions.m_plotFrame = false;
  939. if( !EXPORT_SVG::Plot( brd.get(), svgPlotOptions ) )
  940. m_reporter->Report( _( "Error creating svg file" ) + wxS( "\n" ), RPT_SEVERITY_ERROR );
  941. return CLI::EXIT_CODES::OK;
  942. }
  943. int PCBNEW_JOBS_HANDLER::JobExportDrc( JOB* aJob )
  944. {
  945. JOB_PCB_DRC* drcJob = dynamic_cast<JOB_PCB_DRC*>( aJob );
  946. if( drcJob == nullptr )
  947. return CLI::EXIT_CODES::ERR_UNKNOWN;
  948. if( aJob->IsCli() )
  949. m_reporter->Report( _( "Loading board\n" ), RPT_SEVERITY_INFO );
  950. BOARD* brd = LoadBoard( drcJob->m_filename, true );
  951. brd->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
  952. if( drcJob->m_outputFile.IsEmpty() )
  953. {
  954. wxFileName fn = brd->GetFileName();
  955. fn.SetName( fn.GetName() );
  956. if( drcJob->m_format == JOB_PCB_DRC::OUTPUT_FORMAT::JSON )
  957. fn.SetExt( FILEEXT::JsonFileExtension );
  958. else
  959. fn.SetExt( FILEEXT::ReportFileExtension );
  960. drcJob->m_outputFile = fn.GetFullName();
  961. }
  962. EDA_UNITS units;
  963. switch( drcJob->m_units )
  964. {
  965. case JOB_PCB_DRC::UNITS::INCHES: units = EDA_UNITS::INCHES; break;
  966. case JOB_PCB_DRC::UNITS::MILS: units = EDA_UNITS::MILS; break;
  967. case JOB_PCB_DRC::UNITS::MILLIMETERS: units = EDA_UNITS::MILLIMETRES; break;
  968. default: units = EDA_UNITS::MILLIMETRES; break;
  969. }
  970. std::shared_ptr<DRC_ENGINE> drcEngine = brd->GetDesignSettings().m_DRCEngine;
  971. std::unique_ptr<NETLIST> netlist = std::make_unique<NETLIST>();
  972. drcEngine->SetDrawingSheet( getDrawingSheetProxyView( brd ) );
  973. // BOARD_COMMIT uses TOOL_MANAGER to grab the board internally so we must give it one
  974. TOOL_MANAGER* toolManager = new TOOL_MANAGER;
  975. toolManager->SetEnvironment( brd, nullptr, nullptr, Kiface().KifaceSettings(), nullptr );
  976. BOARD_COMMIT commit( toolManager );
  977. m_reporter->Report( _( "Running DRC...\n" ), RPT_SEVERITY_INFO );
  978. if( drcJob->m_parity )
  979. {
  980. typedef bool (*NETLIST_FN_PTR)( const wxString&, std::string& );
  981. KIFACE* eeschema = m_kiway->KiFACE( KIWAY::FACE_SCH );
  982. wxFileName schematicPath( drcJob->m_filename );
  983. NETLIST_FN_PTR netlister = (NETLIST_FN_PTR) eeschema->IfaceOrAddress( KIFACE_NETLIST_SCHEMATIC );
  984. std::string netlist_str;
  985. schematicPath.SetExt( FILEEXT::KiCadSchematicFileExtension );
  986. if( !schematicPath.Exists() )
  987. schematicPath.SetExt( FILEEXT::LegacySchematicFileExtension );
  988. if( !schematicPath.Exists() )
  989. {
  990. m_reporter->Report( _( "Failed to find schematic for parity tests.\n" ),
  991. RPT_SEVERITY_INFO );
  992. }
  993. else
  994. {
  995. (*netlister)( schematicPath.GetFullPath(), netlist_str );
  996. try
  997. {
  998. auto lineReader = new STRING_LINE_READER( netlist_str, _( "Eeschema netlist" ) );
  999. KICAD_NETLIST_READER netlistReader( lineReader, netlist.get() );
  1000. netlistReader.LoadNetlist();
  1001. }
  1002. catch( const IO_ERROR& )
  1003. {
  1004. m_reporter->Report( _( "Failed to fetch schematic netlist for parity tests.\n" ),
  1005. RPT_SEVERITY_INFO );
  1006. }
  1007. drcEngine->SetSchematicNetlist( netlist.get() );
  1008. }
  1009. }
  1010. drcEngine->SetProgressReporter( nullptr );
  1011. drcEngine->SetViolationHandler(
  1012. [&]( const std::shared_ptr<DRC_ITEM>& aItem, VECTOR2I aPos, int aLayer )
  1013. {
  1014. PCB_MARKER* marker = new PCB_MARKER( aItem, aPos, aLayer );
  1015. commit.Add( marker );
  1016. } );
  1017. brd->RecordDRCExclusions();
  1018. brd->DeleteMARKERs( true, true );
  1019. drcEngine->RunTests( units, drcJob->m_reportAllTrackErrors, drcJob->m_parity );
  1020. drcEngine->ClearViolationHandler();
  1021. commit.Push( _( "DRC" ), SKIP_UNDO | SKIP_SET_DIRTY );
  1022. // Update the exclusion status on any excluded markers that still exist.
  1023. brd->ResolveDRCExclusions( false );
  1024. std::shared_ptr<DRC_ITEMS_PROVIDER> markersProvider = std::make_shared<DRC_ITEMS_PROVIDER>(
  1025. brd, MARKER_BASE::MARKER_DRC, MARKER_BASE::MARKER_DRAWING_SHEET );
  1026. std::shared_ptr<DRC_ITEMS_PROVIDER> ratsnestProvider =
  1027. std::make_shared<DRC_ITEMS_PROVIDER>( brd, MARKER_BASE::MARKER_RATSNEST );
  1028. std::shared_ptr<DRC_ITEMS_PROVIDER> fpWarningsProvider =
  1029. std::make_shared<DRC_ITEMS_PROVIDER>( brd, MARKER_BASE::MARKER_PARITY );
  1030. markersProvider->SetSeverities( drcJob->m_severity );
  1031. ratsnestProvider->SetSeverities( drcJob->m_severity );
  1032. fpWarningsProvider->SetSeverities( drcJob->m_severity );
  1033. m_reporter->Report( wxString::Format( _( "Found %d violations\n" ),
  1034. markersProvider->GetCount() ),
  1035. RPT_SEVERITY_INFO );
  1036. m_reporter->Report( wxString::Format( _( "Found %d unconnected items\n" ),
  1037. ratsnestProvider->GetCount() ),
  1038. RPT_SEVERITY_INFO );
  1039. if( drcJob->m_parity )
  1040. {
  1041. m_reporter->Report( wxString::Format( _( "Found %d schematic parity issues\n" ),
  1042. fpWarningsProvider->GetCount() ),
  1043. RPT_SEVERITY_INFO );
  1044. }
  1045. DRC_REPORT reportWriter( brd, units, markersProvider, ratsnestProvider, fpWarningsProvider );
  1046. bool wroteReport = false;
  1047. if( drcJob->m_format == JOB_PCB_DRC::OUTPUT_FORMAT::JSON )
  1048. wroteReport = reportWriter.WriteJsonReport( drcJob->m_outputFile );
  1049. else
  1050. wroteReport = reportWriter.WriteTextReport( drcJob->m_outputFile );
  1051. if( !wroteReport )
  1052. {
  1053. m_reporter->Report( wxString::Format( _( "Unable to save DRC report to %s\n" ),
  1054. drcJob->m_outputFile ),
  1055. RPT_SEVERITY_INFO );
  1056. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  1057. }
  1058. m_reporter->Report( wxString::Format( _( "Saved DRC Report to %s\n" ),
  1059. drcJob->m_outputFile ),
  1060. RPT_SEVERITY_INFO );
  1061. if( drcJob->m_exitCodeViolations )
  1062. {
  1063. if( markersProvider->GetCount() > 0 || ratsnestProvider->GetCount() > 0
  1064. || fpWarningsProvider->GetCount() > 0 )
  1065. {
  1066. return CLI::EXIT_CODES::ERR_RC_VIOLATIONS;
  1067. }
  1068. }
  1069. return CLI::EXIT_CODES::SUCCESS;
  1070. }
  1071. int PCBNEW_JOBS_HANDLER::JobExportIpc2581( JOB* aJob )
  1072. {
  1073. JOB_EXPORT_PCB_IPC2581* job = dynamic_cast<JOB_EXPORT_PCB_IPC2581*>( aJob );
  1074. if( job == nullptr )
  1075. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1076. if( job->IsCli() )
  1077. m_reporter->Report( _( "Loading board\n" ), RPT_SEVERITY_INFO );
  1078. BOARD* brd = LoadBoard( job->m_filename, true );
  1079. if( job->m_outputFile.IsEmpty() )
  1080. {
  1081. wxFileName fn = brd->GetFileName();
  1082. fn.SetName( fn.GetName() );
  1083. fn.SetExt( FILEEXT::Ipc2581FileExtension );
  1084. job->m_outputFile = fn.GetFullName();
  1085. }
  1086. STRING_UTF8_MAP props;
  1087. props["units"] = job->m_units == JOB_EXPORT_PCB_IPC2581::IPC2581_UNITS::MILLIMETERS ? "mm"
  1088. : "inch";
  1089. props["sigfig"] = wxString::Format( "%d", job->m_units );
  1090. props["version"] = job->m_version == JOB_EXPORT_PCB_IPC2581::IPC2581_VERSION::C ? "C" : "B";
  1091. props["OEMRef"] = job->m_colInternalId;
  1092. props["mpn"] = job->m_colMfgPn;
  1093. props["mfg"] = job->m_colMfg;
  1094. props["dist"] = job->m_colDist;
  1095. props["distpn"] = job->m_colDistPn;
  1096. wxString tempFile = wxFileName::CreateTempFileName( wxS( "pcbnew_ipc" ) );
  1097. try
  1098. {
  1099. IO_RELEASER<PCB_IO> pi( PCB_IO_MGR::PluginFind( PCB_IO_MGR::IPC2581 ) );
  1100. pi->SetProgressReporter( m_progressReporter );
  1101. pi->SaveBoard( tempFile, brd, &props );
  1102. }
  1103. catch( const IO_ERROR& ioe )
  1104. {
  1105. m_reporter->Report( wxString::Format( _( "Error generating IPC2581 file '%s'.\n%s" ),
  1106. job->m_filename, ioe.What() ),
  1107. RPT_SEVERITY_ERROR );
  1108. wxRemoveFile( tempFile );
  1109. return CLI::EXIT_CODES::ERR_UNKNOWN;
  1110. }
  1111. if( job->m_compress )
  1112. {
  1113. wxFileName tempfn = job->m_outputFile;
  1114. tempfn.SetExt( FILEEXT::Ipc2581FileExtension );
  1115. wxFileName zipfn = tempFile;
  1116. zipfn.SetExt( "zip" );
  1117. wxFFileOutputStream fnout( zipfn.GetFullPath() );
  1118. wxZipOutputStream zip( fnout );
  1119. wxFFileInputStream fnin( tempFile );
  1120. zip.PutNextEntry( tempfn.GetFullName() );
  1121. fnin.Read( zip );
  1122. zip.Close();
  1123. fnout.Close();
  1124. wxRemoveFile( tempFile );
  1125. tempFile = zipfn.GetFullPath();
  1126. }
  1127. // If save succeeded, replace the original with what we just wrote
  1128. if( !wxRenameFile( tempFile, job->m_outputFile ) )
  1129. {
  1130. m_reporter->Report( wxString::Format( _( "Error generating IPC2581 file '%s'.\n"
  1131. "Failed to rename temporary file '%s." )
  1132. + wxS( "\n" ),
  1133. job->m_outputFile, tempFile ),
  1134. RPT_SEVERITY_ERROR );
  1135. }
  1136. return CLI::EXIT_CODES::SUCCESS;
  1137. }
  1138. DS_PROXY_VIEW_ITEM* PCBNEW_JOBS_HANDLER::getDrawingSheetProxyView( BOARD* aBrd )
  1139. {
  1140. DS_PROXY_VIEW_ITEM* drawingSheet = new DS_PROXY_VIEW_ITEM( pcbIUScale,
  1141. &aBrd->GetPageSettings(),
  1142. aBrd->GetProject(),
  1143. &aBrd->GetTitleBlock(),
  1144. &aBrd->GetProperties() );
  1145. drawingSheet->SetSheetName( std::string() );
  1146. drawingSheet->SetSheetPath( std::string() );
  1147. drawingSheet->SetIsFirstPage( true );
  1148. drawingSheet->SetFileName( TO_UTF8( aBrd->GetFileName() ) );
  1149. return drawingSheet;
  1150. }
  1151. void PCBNEW_JOBS_HANDLER::loadOverrideDrawingSheet( BOARD* aBrd, const wxString& aSheetPath )
  1152. {
  1153. // dont bother attempting to load a empty path, if there was one
  1154. if( aSheetPath.IsEmpty() )
  1155. return;
  1156. auto loadSheet =
  1157. [&]( const wxString& path ) -> bool
  1158. {
  1159. BASE_SCREEN::m_DrawingSheetFileName = path;
  1160. wxString filename = DS_DATA_MODEL::ResolvePath( BASE_SCREEN::m_DrawingSheetFileName,
  1161. aBrd->GetProject()->GetProjectPath() );
  1162. if( !DS_DATA_MODEL::GetTheInstance().LoadDrawingSheet( filename ) )
  1163. {
  1164. m_reporter->Report( wxString::Format( _( "Error loading drawing sheet '%s'." ) + wxS( "\n" ),
  1165. path ),
  1166. RPT_SEVERITY_ERROR );
  1167. return false;
  1168. }
  1169. return true;
  1170. };
  1171. if( loadSheet( aSheetPath ) )
  1172. return;
  1173. // failed loading custom path, revert back to default
  1174. loadSheet( aBrd->GetProject()->GetProjectFile().m_BoardDrawingSheetFile );
  1175. }