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.

1069 lines
37 KiB

3 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
2 years ago
3 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 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-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 "eeschema_jobs_handler.h"
  21. #include <common.h>
  22. #include <pgm_base.h>
  23. #include <cli/exit_codes.h>
  24. #include <sch_plotter.h>
  25. #include <drawing_sheet/ds_proxy_view_item.h>
  26. #include <jobs/job_export_sch_bom.h>
  27. #include <jobs/job_export_sch_pythonbom.h>
  28. #include <jobs/job_export_sch_netlist.h>
  29. #include <jobs/job_export_sch_plot.h>
  30. #include <jobs/job_sch_erc.h>
  31. #include <jobs/job_sym_export_svg.h>
  32. #include <jobs/job_sym_upgrade.h>
  33. #include <schematic.h>
  34. #include <wx/dir.h>
  35. #include <wx/file.h>
  36. #include <memory>
  37. #include <connection_graph.h>
  38. #include "eeschema_helpers.h"
  39. #include <kiway.h>
  40. #include <sch_painter.h>
  41. #include <locale_io.h>
  42. #include <erc.h>
  43. #include <erc_report.h>
  44. #include <wildcards_and_files_ext.h>
  45. #include <plotters/plotters_pslike.h>
  46. #include <drawing_sheet/ds_data_model.h>
  47. #include <reporter.h>
  48. #include <string_utils.h>
  49. #include <settings/settings_manager.h>
  50. #include <sch_file_versions.h>
  51. #include <sch_io/kicad_sexpr/sch_io_kicad_sexpr_lib_cache.h>
  52. #include <netlist.h>
  53. #include <netlist_exporter_base.h>
  54. #include <netlist_exporter_orcadpcb2.h>
  55. #include <netlist_exporter_cadstar.h>
  56. #include <netlist_exporter_spice.h>
  57. #include <netlist_exporter_spice_model.h>
  58. #include <netlist_exporter_kicad.h>
  59. #include <netlist_exporter_xml.h>
  60. #include <netlist_exporter_pads.h>
  61. #include <netlist_exporter_allegro.h>
  62. #include <fields_data_model.h>
  63. EESCHEMA_JOBS_HANDLER::EESCHEMA_JOBS_HANDLER( KIWAY* aKiway ) :
  64. JOB_DISPATCHER( aKiway )
  65. {
  66. Register( "bom",
  67. std::bind( &EESCHEMA_JOBS_HANDLER::JobExportBom, this, std::placeholders::_1 ) );
  68. Register( "pythonbom",
  69. std::bind( &EESCHEMA_JOBS_HANDLER::JobExportPythonBom, this,
  70. std::placeholders::_1 ) );
  71. Register( "netlist",
  72. std::bind( &EESCHEMA_JOBS_HANDLER::JobExportNetlist, this, std::placeholders::_1 ) );
  73. Register( "plot",
  74. std::bind( &EESCHEMA_JOBS_HANDLER::JobExportPlot, this, std::placeholders::_1 ) );
  75. Register( "symupgrade",
  76. std::bind( &EESCHEMA_JOBS_HANDLER::JobSymUpgrade, this, std::placeholders::_1 ) );
  77. Register( "symsvg",
  78. std::bind( &EESCHEMA_JOBS_HANDLER::JobSymExportSvg, this, std::placeholders::_1 ) );
  79. Register( "erc",
  80. std::bind( &EESCHEMA_JOBS_HANDLER::JobSchErc, this, std::placeholders::_1 ) );
  81. }
  82. void EESCHEMA_JOBS_HANDLER::InitRenderSettings( KIGFX::SCH_RENDER_SETTINGS* aRenderSettings,
  83. const wxString& aTheme, SCHEMATIC* aSch,
  84. const wxString& aDrawingSheetOverride )
  85. {
  86. COLOR_SETTINGS* cs = Pgm().GetSettingsManager().GetColorSettings( aTheme );
  87. aRenderSettings->LoadColors( cs );
  88. aRenderSettings->SetDefaultPenWidth( aSch->Settings().m_DefaultLineWidth );
  89. aRenderSettings->m_LabelSizeRatio = aSch->Settings().m_LabelSizeRatio;
  90. aRenderSettings->m_TextOffsetRatio = aSch->Settings().m_TextOffsetRatio;
  91. aRenderSettings->m_PinSymbolSize = aSch->Settings().m_PinSymbolSize;
  92. aRenderSettings->SetDashLengthRatio( aSch->Settings().m_DashedLineDashRatio );
  93. aRenderSettings->SetGapLengthRatio( aSch->Settings().m_DashedLineGapRatio );
  94. // Load the drawing sheet from the filename stored in BASE_SCREEN::m_DrawingSheetFileName.
  95. // If empty, or not existing, the default drawing sheet is loaded.
  96. auto loadSheet =
  97. [&]( const wxString& path ) -> bool
  98. {
  99. wxString absolutePath = DS_DATA_MODEL::ResolvePath( path,
  100. aSch->Prj().GetProjectPath() );
  101. if( !DS_DATA_MODEL::GetTheInstance().LoadDrawingSheet( absolutePath ) )
  102. {
  103. m_reporter->Report( wxString::Format( _( "Error loading drawing sheet '%s'." ),
  104. path ),
  105. RPT_SEVERITY_ERROR );
  106. return false;
  107. }
  108. return true;
  109. };
  110. // try to load the override first
  111. if( !aDrawingSheetOverride.IsEmpty() && loadSheet( aDrawingSheetOverride ) )
  112. return;
  113. // no override or failed override continues here
  114. loadSheet( aSch->Settings().m_SchDrawingSheetFileName );
  115. }
  116. int EESCHEMA_JOBS_HANDLER::JobExportPlot( JOB* aJob )
  117. {
  118. JOB_EXPORT_SCH_PLOT* aPlotJob = dynamic_cast<JOB_EXPORT_SCH_PLOT*>( aJob );
  119. if( !aPlotJob )
  120. return CLI::EXIT_CODES::ERR_UNKNOWN;
  121. SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( aPlotJob->m_filename, SCH_IO_MGR::SCH_KICAD, true );
  122. if( sch == nullptr )
  123. {
  124. m_reporter->Report( _( "Failed to load schematic file\n" ), RPT_SEVERITY_ERROR );
  125. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  126. }
  127. sch->Prj().ApplyTextVars( aJob->GetVarOverrides() );
  128. std::unique_ptr<KIGFX::SCH_RENDER_SETTINGS> renderSettings =
  129. std::make_unique<KIGFX::SCH_RENDER_SETTINGS>();
  130. InitRenderSettings( renderSettings.get(), aPlotJob->m_theme, sch, aPlotJob->m_drawingSheet );
  131. std::unique_ptr<SCH_PLOTTER> schPlotter = std::make_unique<SCH_PLOTTER>( sch );
  132. PLOT_FORMAT format = PLOT_FORMAT::PDF;
  133. switch( aPlotJob->m_plotFormat )
  134. {
  135. case SCH_PLOT_FORMAT::DXF: format = PLOT_FORMAT::DXF; break;
  136. case SCH_PLOT_FORMAT::PDF: format = PLOT_FORMAT::PDF; break;
  137. case SCH_PLOT_FORMAT::SVG: format = PLOT_FORMAT::SVG; break;
  138. case SCH_PLOT_FORMAT::POST: format = PLOT_FORMAT::POST; break;
  139. case SCH_PLOT_FORMAT::HPGL: format = PLOT_FORMAT::HPGL; break;
  140. case SCH_PLOT_FORMAT::GERBER: format = PLOT_FORMAT::GERBER; break;
  141. }
  142. HPGL_PAGE_SIZE hpglPageSize = HPGL_PAGE_SIZE::DEFAULT;
  143. switch( aPlotJob->m_HPGLPaperSizeSelect )
  144. {
  145. case JOB_HPGL_PAGE_SIZE::DEFAULT: hpglPageSize = HPGL_PAGE_SIZE::DEFAULT; break;
  146. case JOB_HPGL_PAGE_SIZE::SIZE_A: hpglPageSize = HPGL_PAGE_SIZE::SIZE_A; break;
  147. case JOB_HPGL_PAGE_SIZE::SIZE_A0: hpglPageSize = HPGL_PAGE_SIZE::SIZE_A0; break;
  148. case JOB_HPGL_PAGE_SIZE::SIZE_A1: hpglPageSize = HPGL_PAGE_SIZE::SIZE_A1; break;
  149. case JOB_HPGL_PAGE_SIZE::SIZE_A2: hpglPageSize = HPGL_PAGE_SIZE::SIZE_A2; break;
  150. case JOB_HPGL_PAGE_SIZE::SIZE_A3: hpglPageSize = HPGL_PAGE_SIZE::SIZE_A3; break;
  151. case JOB_HPGL_PAGE_SIZE::SIZE_A4: hpglPageSize = HPGL_PAGE_SIZE::SIZE_A4; break;
  152. case JOB_HPGL_PAGE_SIZE::SIZE_A5: hpglPageSize = HPGL_PAGE_SIZE::SIZE_A5; break;
  153. case JOB_HPGL_PAGE_SIZE::SIZE_B: hpglPageSize = HPGL_PAGE_SIZE::SIZE_B; break;
  154. case JOB_HPGL_PAGE_SIZE::SIZE_C: hpglPageSize = HPGL_PAGE_SIZE::SIZE_C; break;
  155. case JOB_HPGL_PAGE_SIZE::SIZE_D: hpglPageSize = HPGL_PAGE_SIZE::SIZE_D; break;
  156. case JOB_HPGL_PAGE_SIZE::SIZE_E: hpglPageSize = HPGL_PAGE_SIZE::SIZE_E; break;
  157. }
  158. HPGL_PLOT_ORIGIN_AND_UNITS hpglOrigin = HPGL_PLOT_ORIGIN_AND_UNITS::USER_FIT_PAGE;
  159. switch( aPlotJob->m_HPGLPlotOrigin )
  160. {
  161. case JOB_HPGL_PLOT_ORIGIN_AND_UNITS::PLOTTER_BOT_LEFT:
  162. hpglOrigin = HPGL_PLOT_ORIGIN_AND_UNITS::PLOTTER_BOT_LEFT;
  163. break;
  164. case JOB_HPGL_PLOT_ORIGIN_AND_UNITS::PLOTTER_CENTER:
  165. hpglOrigin = HPGL_PLOT_ORIGIN_AND_UNITS::PLOTTER_CENTER;
  166. break;
  167. case JOB_HPGL_PLOT_ORIGIN_AND_UNITS::USER_FIT_CONTENT:
  168. hpglOrigin = HPGL_PLOT_ORIGIN_AND_UNITS::USER_FIT_CONTENT;
  169. break;
  170. case JOB_HPGL_PLOT_ORIGIN_AND_UNITS::USER_FIT_PAGE:
  171. hpglOrigin = HPGL_PLOT_ORIGIN_AND_UNITS::USER_FIT_PAGE;
  172. break;
  173. }
  174. int pageSizeSelect = PageFormatReq::PAGE_SIZE_AUTO;
  175. switch( aPlotJob->m_pageSizeSelect )
  176. {
  177. case JOB_PAGE_SIZE::PAGE_SIZE_A: pageSizeSelect = PageFormatReq::PAGE_SIZE_A; break;
  178. case JOB_PAGE_SIZE::PAGE_SIZE_A4: pageSizeSelect = PageFormatReq::PAGE_SIZE_A4; break;
  179. case JOB_PAGE_SIZE::PAGE_SIZE_AUTO: pageSizeSelect = PageFormatReq::PAGE_SIZE_AUTO; break;
  180. }
  181. SCH_PLOT_SETTINGS settings;
  182. settings.m_blackAndWhite = aPlotJob->m_blackAndWhite;
  183. settings.m_HPGLPaperSizeSelect = hpglPageSize;
  184. settings.m_HPGLPenSize = aPlotJob->m_HPGLPenSize;
  185. settings.m_HPGLPlotOrigin = hpglOrigin;
  186. settings.m_PDFPropertyPopups = aPlotJob->m_PDFPropertyPopups;
  187. settings.m_PDFMetadata = aPlotJob->m_PDFMetadata;
  188. settings.m_outputDirectory = aPlotJob->m_outputDirectory;
  189. settings.m_outputFile = aPlotJob->m_outputFile;
  190. settings.m_pageSizeSelect = pageSizeSelect;
  191. settings.m_plotAll = aPlotJob->m_plotAll;
  192. settings.m_plotDrawingSheet = aPlotJob->m_plotDrawingSheet;
  193. settings.m_plotPages = aPlotJob->m_plotPages;
  194. settings.m_theme = aPlotJob->m_theme;
  195. settings.m_useBackgroundColor = aPlotJob->m_useBackgroundColor;
  196. schPlotter->Plot( format, settings, renderSettings.get(), m_reporter );
  197. return CLI::EXIT_CODES::OK;
  198. }
  199. int EESCHEMA_JOBS_HANDLER::JobExportNetlist( JOB* aJob )
  200. {
  201. JOB_EXPORT_SCH_NETLIST* aNetJob = dynamic_cast<JOB_EXPORT_SCH_NETLIST*>( aJob );
  202. if( !aNetJob )
  203. return CLI::EXIT_CODES::ERR_UNKNOWN;
  204. SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( aNetJob->m_filename, SCH_IO_MGR::SCH_KICAD, true );
  205. if( sch == nullptr )
  206. {
  207. m_reporter->Report( _( "Failed to load schematic file\n" ), RPT_SEVERITY_ERROR );
  208. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  209. }
  210. // Annotation warning check
  211. SCH_REFERENCE_LIST referenceList;
  212. sch->GetSheets().GetSymbols( referenceList );
  213. if( referenceList.GetCount() > 0 )
  214. {
  215. if( referenceList.CheckAnnotation(
  216. []( ERCE_T, const wxString&, SCH_REFERENCE*, SCH_REFERENCE* )
  217. {
  218. // We're only interested in the end result -- either errors or not
  219. } )
  220. > 0 )
  221. {
  222. m_reporter->Report( _( "Warning: schematic has annotation errors, please use the "
  223. "schematic editor to fix them\n" ),
  224. RPT_SEVERITY_WARNING );
  225. }
  226. }
  227. // Test duplicate sheet names:
  228. ERC_TESTER erc( sch );
  229. if( erc.TestDuplicateSheetNames( false ) > 0 )
  230. m_reporter->Report( _( "Warning: duplicate sheet names.\n" ), RPT_SEVERITY_WARNING );
  231. std::unique_ptr<NETLIST_EXPORTER_BASE> helper;
  232. unsigned netlistOption = 0;
  233. wxString fileExt;
  234. switch( aNetJob->format )
  235. {
  236. case JOB_EXPORT_SCH_NETLIST::FORMAT::KICADSEXPR:
  237. fileExt = FILEEXT::NetlistFileExtension;
  238. helper = std::make_unique<NETLIST_EXPORTER_KICAD>( sch );
  239. break;
  240. case JOB_EXPORT_SCH_NETLIST::FORMAT::ORCADPCB2:
  241. fileExt = FILEEXT::OrCadPcb2NetlistFileExtension;
  242. helper = std::make_unique<NETLIST_EXPORTER_ORCADPCB2>( sch );
  243. break;
  244. case JOB_EXPORT_SCH_NETLIST::FORMAT::CADSTAR:
  245. fileExt = FILEEXT::CadstarNetlistFileExtension;
  246. helper = std::make_unique<NETLIST_EXPORTER_CADSTAR>( sch );
  247. break;
  248. case JOB_EXPORT_SCH_NETLIST::FORMAT::SPICE:
  249. fileExt = FILEEXT::SpiceFileExtension;
  250. netlistOption = NETLIST_EXPORTER_SPICE::OPTION_SIM_COMMAND;
  251. helper = std::make_unique<NETLIST_EXPORTER_SPICE>( sch );
  252. break;
  253. case JOB_EXPORT_SCH_NETLIST::FORMAT::SPICEMODEL:
  254. fileExt = FILEEXT::SpiceFileExtension;
  255. helper = std::make_unique<NETLIST_EXPORTER_SPICE_MODEL>( sch );
  256. break;
  257. case JOB_EXPORT_SCH_NETLIST::FORMAT::KICADXML:
  258. fileExt = wxS( "xml" );
  259. helper = std::make_unique<NETLIST_EXPORTER_XML>( sch );
  260. break;
  261. case JOB_EXPORT_SCH_NETLIST::FORMAT::PADS:
  262. fileExt = wxS( "asc" );
  263. helper = std::make_unique<NETLIST_EXPORTER_PADS>( sch );
  264. break;
  265. case JOB_EXPORT_SCH_NETLIST::FORMAT::ALLEGRO:
  266. fileExt = wxS( "txt" );
  267. helper = std::make_unique<NETLIST_EXPORTER_ALLEGRO>( sch );
  268. break;
  269. default:
  270. m_reporter->Report( _( "Unknown netlist format.\n" ), RPT_SEVERITY_ERROR );
  271. return CLI::EXIT_CODES::ERR_UNKNOWN;
  272. }
  273. if( aNetJob->m_outputFile.IsEmpty() )
  274. {
  275. wxFileName fn = sch->GetFileName();
  276. fn.SetName( fn.GetName() );
  277. fn.SetExt( fileExt );
  278. aNetJob->m_outputFile = fn.GetFullName();
  279. }
  280. bool res = helper->WriteNetlist( aNetJob->m_outputFile, netlistOption, *m_reporter );
  281. if( !res )
  282. return CLI::EXIT_CODES::ERR_UNKNOWN;
  283. return CLI::EXIT_CODES::OK;
  284. }
  285. int EESCHEMA_JOBS_HANDLER::JobExportBom( JOB* aJob )
  286. {
  287. JOB_EXPORT_SCH_BOM* aBomJob = dynamic_cast<JOB_EXPORT_SCH_BOM*>( aJob );
  288. if( !aBomJob )
  289. return CLI::EXIT_CODES::ERR_UNKNOWN;
  290. SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( aBomJob->m_filename, SCH_IO_MGR::SCH_KICAD, true );
  291. if( sch == nullptr )
  292. {
  293. m_reporter->Report( _( "Failed to load schematic file\n" ), RPT_SEVERITY_ERROR );
  294. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  295. }
  296. sch->Prj().ApplyTextVars( aJob->GetVarOverrides() );
  297. // Annotation warning check
  298. SCH_REFERENCE_LIST referenceList;
  299. sch->GetSheets().GetSymbols( referenceList, false, false );
  300. if( referenceList.GetCount() > 0 )
  301. {
  302. SCH_REFERENCE_LIST copy = referenceList;
  303. // Check annotation splits references...
  304. if( copy.CheckAnnotation(
  305. []( ERCE_T, const wxString&, SCH_REFERENCE*, SCH_REFERENCE* )
  306. {
  307. // We're only interested in the end result -- either errors or not
  308. } )
  309. > 0 )
  310. {
  311. m_reporter->Report(
  312. _( "Warning: schematic has annotation errors, please use the schematic "
  313. "editor to fix them\n" ),
  314. RPT_SEVERITY_WARNING );
  315. }
  316. }
  317. // Test duplicate sheet names:
  318. ERC_TESTER erc( sch );
  319. if( erc.TestDuplicateSheetNames( false ) > 0 )
  320. m_reporter->Report( _( "Warning: duplicate sheet names.\n" ), RPT_SEVERITY_WARNING );
  321. // Build our data model
  322. FIELDS_EDITOR_GRID_DATA_MODEL dataModel( referenceList );
  323. // Mandatory fields + quantity virtual field first
  324. for( int i = 0; i < MANDATORY_FIELDS; ++i )
  325. dataModel.AddColumn( TEMPLATE_FIELDNAME::GetDefaultFieldName( i ),
  326. TEMPLATE_FIELDNAME::GetDefaultFieldName( i, true ), false );
  327. // User field names in symbols second
  328. std::set<wxString> userFieldNames;
  329. for( size_t i = 0; i < referenceList.GetCount(); ++i )
  330. {
  331. SCH_SYMBOL* symbol = referenceList[i].GetSymbol();
  332. for( int j = MANDATORY_FIELDS; j < symbol->GetFieldCount(); ++j )
  333. userFieldNames.insert( symbol->GetFields()[j].GetName() );
  334. }
  335. for( const wxString& fieldName : userFieldNames )
  336. dataModel.AddColumn( fieldName, GetTextVars( fieldName ), true );
  337. // Add any templateFieldNames which aren't already present in the userFieldNames
  338. for( const TEMPLATE_FIELDNAME& templateFieldname :
  339. sch->Settings().m_TemplateFieldNames.GetTemplateFieldNames() )
  340. {
  341. if( userFieldNames.count( templateFieldname.m_Name ) == 0 )
  342. {
  343. dataModel.AddColumn( templateFieldname.m_Name, GetTextVars( templateFieldname.m_Name ),
  344. false );
  345. }
  346. }
  347. BOM_PRESET preset;
  348. // Load a preset if one is specified
  349. if( !aBomJob->m_bomPresetName.IsEmpty() )
  350. {
  351. // Make sure the built-in presets are loaded
  352. for( const BOM_PRESET& p : BOM_PRESET::BuiltInPresets() )
  353. sch->Settings().m_BomPresets.emplace_back( p );
  354. // Find the preset
  355. BOM_PRESET* schPreset = nullptr;
  356. for( BOM_PRESET& p : sch->Settings().m_BomPresets )
  357. {
  358. if( p.name == aBomJob->m_bomPresetName )
  359. {
  360. schPreset = &p;
  361. break;
  362. }
  363. }
  364. if( !schPreset )
  365. {
  366. m_reporter->Report( wxString::Format( _( "BOM preset '%s' not found" ) + wxS( "\n" ),
  367. aBomJob->m_bomPresetName ),
  368. RPT_SEVERITY_ERROR );
  369. return CLI::EXIT_CODES::ERR_UNKNOWN;
  370. }
  371. preset = *schPreset;
  372. }
  373. else
  374. {
  375. size_t i = 0;
  376. for( wxString fieldName : aBomJob->m_fieldsOrdered )
  377. {
  378. // Handle wildcard. We allow the wildcard anywhere in the list, but it needs to respect
  379. // fields that come before and after the wildcard.
  380. if( fieldName == wxS( "*" ) )
  381. {
  382. for( const BOM_FIELD& modelField : dataModel.GetFieldsOrdered() )
  383. {
  384. struct BOM_FIELD field;
  385. field.name = modelField.name;
  386. field.show = true;
  387. field.groupBy = false;
  388. field.label = field.name;
  389. bool fieldAlreadyPresent = false;
  390. for( BOM_FIELD& presetField : preset.fieldsOrdered )
  391. {
  392. if( presetField.name == field.name )
  393. {
  394. fieldAlreadyPresent = true;
  395. break;
  396. }
  397. }
  398. bool fieldLaterInList = false;
  399. for( const wxString& fieldInList : aBomJob->m_fieldsOrdered )
  400. {
  401. if( fieldInList == field.name )
  402. {
  403. fieldLaterInList = true;
  404. break;
  405. }
  406. }
  407. if( !fieldAlreadyPresent && !fieldLaterInList )
  408. preset.fieldsOrdered.emplace_back( field );
  409. }
  410. continue;
  411. }
  412. struct BOM_FIELD field;
  413. field.name = fieldName;
  414. field.show = true;
  415. field.groupBy = std::find( aBomJob->m_fieldsGroupBy.begin(),
  416. aBomJob->m_fieldsGroupBy.end(), field.name )
  417. != aBomJob->m_fieldsGroupBy.end();
  418. if( ( aBomJob->m_fieldsLabels.size() > i ) && !aBomJob->m_fieldsLabels[i].IsEmpty() )
  419. field.label = aBomJob->m_fieldsLabels[i];
  420. else if( IsTextVar( field.name ) )
  421. field.label = GetTextVars( field.name );
  422. else
  423. field.label = field.name;
  424. preset.fieldsOrdered.emplace_back( field );
  425. i++;
  426. }
  427. preset.sortAsc = aBomJob->m_sortAsc;
  428. preset.sortField = aBomJob->m_sortField;
  429. preset.filterString = aBomJob->m_filterString;
  430. preset.groupSymbols = ( aBomJob->m_fieldsGroupBy.size() > 0 );
  431. preset.excludeDNP = aBomJob->m_excludeDNP;
  432. }
  433. dataModel.ApplyBomPreset( preset );
  434. if( aBomJob->m_outputFile.IsEmpty() )
  435. {
  436. wxFileName fn = sch->GetFileName();
  437. fn.SetName( fn.GetName() );
  438. fn.SetExt( FILEEXT::CsvFileExtension );
  439. aBomJob->m_outputFile = fn.GetFullName();
  440. }
  441. wxFile f;
  442. if( !f.Open( aBomJob->m_outputFile, wxFile::write ) )
  443. {
  444. m_reporter->Report( wxString::Format( _( "Unable to open destination '%s'" ),
  445. aBomJob->m_outputFile ),
  446. RPT_SEVERITY_ERROR );
  447. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  448. }
  449. BOM_FMT_PRESET fmt;
  450. // Load a format preset if one is specified
  451. if( !aBomJob->m_bomFmtPresetName.IsEmpty() )
  452. {
  453. // Make sure the built-in presets are loaded
  454. for( const BOM_FMT_PRESET& p : BOM_FMT_PRESET::BuiltInPresets() )
  455. sch->Settings().m_BomFmtPresets.emplace_back( p );
  456. // Find the preset
  457. BOM_FMT_PRESET* schFmtPreset = nullptr;
  458. for( BOM_FMT_PRESET& p : sch->Settings().m_BomFmtPresets )
  459. {
  460. if( p.name == aBomJob->m_bomFmtPresetName )
  461. {
  462. schFmtPreset = &p;
  463. break;
  464. }
  465. }
  466. if( !schFmtPreset )
  467. {
  468. m_reporter->Report(
  469. wxString::Format( _( "BOM format preset '%s' not found" ) + wxS( "\n" ),
  470. aBomJob->m_bomFmtPresetName ),
  471. RPT_SEVERITY_ERROR );
  472. return CLI::EXIT_CODES::ERR_UNKNOWN;
  473. }
  474. fmt = *schFmtPreset;
  475. }
  476. else
  477. {
  478. fmt.fieldDelimiter = aBomJob->m_fieldDelimiter;
  479. fmt.stringDelimiter = aBomJob->m_stringDelimiter;
  480. fmt.refDelimiter = aBomJob->m_refDelimiter;
  481. fmt.refRangeDelimiter = aBomJob->m_refRangeDelimiter;
  482. fmt.keepTabs = aBomJob->m_keepTabs;
  483. fmt.keepLineBreaks = aBomJob->m_keepLineBreaks;
  484. }
  485. bool res = f.Write( dataModel.Export( fmt ) );
  486. if( !res )
  487. return CLI::EXIT_CODES::ERR_UNKNOWN;
  488. return CLI::EXIT_CODES::OK;
  489. }
  490. int EESCHEMA_JOBS_HANDLER::JobExportPythonBom( JOB* aJob )
  491. {
  492. JOB_EXPORT_SCH_PYTHONBOM* aNetJob = dynamic_cast<JOB_EXPORT_SCH_PYTHONBOM*>( aJob );
  493. if( !aNetJob )
  494. return CLI::EXIT_CODES::ERR_UNKNOWN;
  495. SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( aNetJob->m_filename, SCH_IO_MGR::SCH_KICAD, true );
  496. if( sch == nullptr )
  497. {
  498. m_reporter->Report( _( "Failed to load schematic file\n" ), RPT_SEVERITY_ERROR );
  499. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  500. }
  501. // Annotation warning check
  502. SCH_REFERENCE_LIST referenceList;
  503. sch->GetSheets().GetSymbols( referenceList );
  504. if( referenceList.GetCount() > 0 )
  505. {
  506. if( referenceList.CheckAnnotation(
  507. []( ERCE_T, const wxString&, SCH_REFERENCE*, SCH_REFERENCE* )
  508. {
  509. // We're only interested in the end result -- either errors or not
  510. } )
  511. > 0 )
  512. {
  513. m_reporter->Report(
  514. _( "Warning: schematic has annotation errors, please use the schematic "
  515. "editor to fix them\n" ),
  516. RPT_SEVERITY_WARNING );
  517. }
  518. }
  519. // Test duplicate sheet names:
  520. ERC_TESTER erc( sch );
  521. if( erc.TestDuplicateSheetNames( false ) > 0 )
  522. m_reporter->Report( _( "Warning: duplicate sheet names.\n" ), RPT_SEVERITY_WARNING );
  523. std::unique_ptr<NETLIST_EXPORTER_XML> xmlNetlist =
  524. std::make_unique<NETLIST_EXPORTER_XML>( sch );
  525. if( aNetJob->m_outputFile.IsEmpty() )
  526. {
  527. wxFileName fn = sch->GetFileName();
  528. fn.SetName( fn.GetName() + "-bom" );
  529. fn.SetExt( FILEEXT::XmlFileExtension );
  530. aNetJob->m_outputFile = fn.GetFullName();
  531. }
  532. bool res = xmlNetlist->WriteNetlist( aNetJob->m_outputFile, GNL_OPT_BOM, *m_reporter );
  533. if( !res )
  534. return CLI::EXIT_CODES::ERR_UNKNOWN;
  535. return CLI::EXIT_CODES::OK;
  536. }
  537. int EESCHEMA_JOBS_HANDLER::doSymExportSvg( JOB_SYM_EXPORT_SVG* aSvgJob,
  538. KIGFX::SCH_RENDER_SETTINGS* aRenderSettings,
  539. LIB_SYMBOL* symbol )
  540. {
  541. wxASSERT( symbol != nullptr );
  542. if( symbol == nullptr )
  543. return CLI::EXIT_CODES::ERR_UNKNOWN;
  544. LIB_SYMBOL* symbolToPlot = symbol;
  545. // if the symbol is an alias, then the draw items are stored in the root symbol
  546. if( symbol->IsAlias() )
  547. {
  548. if( LIB_SYMBOL_SPTR parent = symbol->GetRootSymbol() )
  549. {
  550. symbolToPlot = parent.get();
  551. }
  552. else
  553. {
  554. wxCHECK( false, CLI::EXIT_CODES::ERR_UNKNOWN );
  555. }
  556. }
  557. if( aSvgJob->m_includeHiddenPins )
  558. {
  559. // horrible hack, TODO overhaul the Plot method to handle this
  560. for( LIB_ITEM& item : symbolToPlot->GetDrawItems() )
  561. {
  562. if( item.Type() != LIB_PIN_T )
  563. continue;
  564. LIB_PIN& pin = static_cast<LIB_PIN&>( item );
  565. pin.SetVisible( true );
  566. }
  567. }
  568. // iterate from unit 1, unit 0 would be "all units" which we don't want
  569. for( int unit = 1; unit < symbol->GetUnitCount() + 1; unit++ )
  570. {
  571. for( int bodyStyle = 1; bodyStyle < ( symbol->HasAlternateBodyStyle() ? 2 : 1 ) + 1; ++bodyStyle )
  572. {
  573. wxString filename;
  574. wxFileName fn;
  575. size_t forbidden_char;
  576. fn.SetPath( aSvgJob->m_outputDirectory );
  577. fn.SetExt( FILEEXT::SVGFileExtension );
  578. filename = symbol->GetName().Lower();
  579. while( wxString::npos
  580. != ( forbidden_char = filename.find_first_of(
  581. wxFileName::GetForbiddenChars( wxPATH_DOS ) ) ) )
  582. {
  583. filename = filename.replace( forbidden_char, 1, wxS( '_' ) );
  584. }
  585. //simplify the name if its single unit
  586. if( symbol->GetUnitCount() > 1 )
  587. {
  588. filename += wxString::Format( "_%d", unit );
  589. if( bodyStyle == 2 )
  590. filename += wxS( "_demorgan" );
  591. fn.SetName( filename );
  592. m_reporter->Report( wxString::Format( _( "Plotting symbol '%s' unit %d to '%s'\n" ),
  593. symbol->GetName(), unit, fn.GetFullPath() ),
  594. RPT_SEVERITY_ACTION );
  595. }
  596. else
  597. {
  598. if( bodyStyle == 2 )
  599. filename += wxS( "_demorgan" );
  600. fn.SetName( filename );
  601. m_reporter->Report( wxString::Format( _( "Plotting symbol '%s' to '%s'\n" ),
  602. symbol->GetName(), fn.GetFullPath() ),
  603. RPT_SEVERITY_ACTION );
  604. }
  605. // Get the symbol bounding box to fit the plot page to it
  606. BOX2I symbolBB = symbol->Flatten()->GetUnitBoundingBox( unit, bodyStyle, false );
  607. PAGE_INFO pageInfo( PAGE_INFO::Custom );
  608. pageInfo.SetHeightMils( schIUScale.IUToMils( symbolBB.GetHeight() * 1.2 ) );
  609. pageInfo.SetWidthMils( schIUScale.IUToMils( symbolBB.GetWidth() * 1.2 ) );
  610. SVG_PLOTTER* plotter = new SVG_PLOTTER();
  611. plotter->SetRenderSettings( aRenderSettings );
  612. plotter->SetPageSettings( pageInfo );
  613. plotter->SetColorMode( !aSvgJob->m_blackAndWhite );
  614. VECTOR2I plot_offset;
  615. const double scale = 1.0;
  616. // Currently, plot units are in decimal
  617. plotter->SetViewport( plot_offset, schIUScale.IU_PER_MILS / 10, scale, false );
  618. plotter->SetCreator( wxT( "Eeschema-SVG" ) );
  619. if( !plotter->OpenFile( fn.GetFullPath() ) )
  620. {
  621. m_reporter->Report(
  622. wxString::Format( _( "Unable to open destination '%s'" ) + wxS( "\n" ),
  623. fn.GetFullPath() ),
  624. RPT_SEVERITY_ERROR );
  625. delete plotter;
  626. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  627. }
  628. LOCALE_IO toggle;
  629. plotter->StartPlot( wxT( "1" ) );
  630. bool background = true;
  631. TRANSFORM temp; // Uses default transform
  632. VECTOR2I plotPos;
  633. plotPos.x = pageInfo.GetWidthIU( schIUScale.IU_PER_MILS ) / 2;
  634. plotPos.y = pageInfo.GetHeightIU( schIUScale.IU_PER_MILS ) / 2;
  635. // note, we want the fields from the original symbol pointer (in case of non-alias)
  636. symbolToPlot->Plot( plotter, unit, bodyStyle, background, plotPos, temp, false );
  637. symbol->PlotLibFields( plotter, unit, bodyStyle, background, plotPos, temp, false,
  638. aSvgJob->m_includeHiddenFields );
  639. symbolToPlot->Plot( plotter, unit, bodyStyle, !background, plotPos, temp, false );
  640. symbol->PlotLibFields( plotter, unit, bodyStyle, !background, plotPos, temp, false,
  641. aSvgJob->m_includeHiddenFields );
  642. plotter->EndPlot();
  643. delete plotter;
  644. }
  645. }
  646. return CLI::EXIT_CODES::OK;
  647. }
  648. int EESCHEMA_JOBS_HANDLER::JobSymExportSvg( JOB* aJob )
  649. {
  650. JOB_SYM_EXPORT_SVG* svgJob = dynamic_cast<JOB_SYM_EXPORT_SVG*>( aJob );
  651. if( !svgJob )
  652. return CLI::EXIT_CODES::ERR_UNKNOWN;
  653. wxFileName fn( svgJob->m_libraryPath );
  654. fn.MakeAbsolute();
  655. SCH_IO_KICAD_SEXPR_LIB_CACHE schLibrary( fn.GetFullPath() );
  656. try
  657. {
  658. schLibrary.Load();
  659. }
  660. catch( ... )
  661. {
  662. m_reporter->Report( _( "Unable to load library\n" ), RPT_SEVERITY_ERROR );
  663. return CLI::EXIT_CODES::ERR_UNKNOWN;
  664. }
  665. LIB_SYMBOL* symbol = nullptr;
  666. if( !svgJob->m_symbol.IsEmpty() )
  667. {
  668. // See if the selected symbol exists
  669. symbol = schLibrary.GetSymbol( svgJob->m_symbol );
  670. if( !symbol )
  671. {
  672. m_reporter->Report( _( "There is no symbol selected to save." ) + wxS( "\n" ),
  673. RPT_SEVERITY_ERROR );
  674. return CLI::EXIT_CODES::ERR_ARGS;
  675. }
  676. }
  677. if( !svgJob->m_outputDirectory.IsEmpty() && !wxDir::Exists( svgJob->m_outputDirectory ) )
  678. {
  679. wxFileName::Mkdir( svgJob->m_outputDirectory );
  680. }
  681. KIGFX::SCH_RENDER_SETTINGS renderSettings;
  682. COLOR_SETTINGS* cs = Pgm().GetSettingsManager().GetColorSettings( svgJob->m_colorTheme );
  683. renderSettings.LoadColors( cs );
  684. renderSettings.SetDefaultPenWidth( DEFAULT_LINE_WIDTH_MILS * schIUScale.IU_PER_MILS );
  685. int exitCode = CLI::EXIT_CODES::OK;
  686. if( symbol )
  687. {
  688. exitCode = doSymExportSvg( svgJob, &renderSettings, symbol );
  689. }
  690. else
  691. {
  692. // Just plot all the symbols we can
  693. const LIB_SYMBOL_MAP& libSymMap = schLibrary.GetSymbolMap();
  694. for( const std::pair<const wxString, LIB_SYMBOL*>& entry : libSymMap )
  695. {
  696. exitCode = doSymExportSvg( svgJob, &renderSettings, entry.second );
  697. if( exitCode != CLI::EXIT_CODES::OK )
  698. break;
  699. }
  700. }
  701. return exitCode;
  702. }
  703. int EESCHEMA_JOBS_HANDLER::JobSymUpgrade( JOB* aJob )
  704. {
  705. JOB_SYM_UPGRADE* upgradeJob = dynamic_cast<JOB_SYM_UPGRADE*>( aJob );
  706. if( !upgradeJob )
  707. return CLI::EXIT_CODES::ERR_UNKNOWN;
  708. wxFileName fn( upgradeJob->m_libraryPath );
  709. fn.MakeAbsolute();
  710. SCH_IO_MGR::SCH_FILE_T fileType = SCH_IO_MGR::GuessPluginTypeFromLibPath( fn.GetFullPath() );
  711. if( !upgradeJob->m_outputLibraryPath.IsEmpty() )
  712. {
  713. if( wxFile::Exists( upgradeJob->m_outputLibraryPath ) )
  714. {
  715. m_reporter->Report( _( "Output path must not conflict with existing path\n" ),
  716. RPT_SEVERITY_ERROR );
  717. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  718. }
  719. }
  720. else if( fileType != SCH_IO_MGR::SCH_KICAD )
  721. {
  722. m_reporter->Report( _( "Output path must be specified to convert legacy and non-KiCad libraries\n" ),
  723. RPT_SEVERITY_ERROR );
  724. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  725. }
  726. if( fileType == SCH_IO_MGR::SCH_KICAD )
  727. {
  728. SCH_IO_KICAD_SEXPR_LIB_CACHE schLibrary( fn.GetFullPath() );
  729. try
  730. {
  731. schLibrary.Load();
  732. }
  733. catch( ... )
  734. {
  735. m_reporter->Report( _( "Unable to load library\n" ), RPT_SEVERITY_ERROR );
  736. return CLI::EXIT_CODES::ERR_UNKNOWN;
  737. }
  738. bool shouldSave =
  739. upgradeJob->m_force
  740. || schLibrary.GetFileFormatVersionAtLoad() < SEXPR_SYMBOL_LIB_FILE_VERSION;
  741. if( shouldSave )
  742. {
  743. m_reporter->Report( _( "Saving symbol library in updated format\n" ),
  744. RPT_SEVERITY_ACTION );
  745. try
  746. {
  747. if( !upgradeJob->m_outputLibraryPath.IsEmpty() )
  748. {
  749. schLibrary.SetFileName( upgradeJob->m_outputLibraryPath );
  750. }
  751. schLibrary.SetModified();
  752. schLibrary.Save();
  753. }
  754. catch( ... )
  755. {
  756. m_reporter->Report( ( "Unable to save library\n" ), RPT_SEVERITY_ERROR );
  757. return CLI::EXIT_CODES::ERR_UNKNOWN;
  758. }
  759. }
  760. else
  761. {
  762. m_reporter->Report( _( "Symbol library was not updated\n" ), RPT_SEVERITY_INFO );
  763. }
  764. }
  765. else
  766. {
  767. if( !SCH_IO_MGR::ConvertLibrary( nullptr, fn.GetAbsolutePath(), upgradeJob->m_outputLibraryPath ) )
  768. {
  769. m_reporter->Report( ( "Unable to convert library\n" ), RPT_SEVERITY_ERROR );
  770. return CLI::EXIT_CODES::ERR_UNKNOWN;
  771. }
  772. }
  773. return CLI::EXIT_CODES::OK;
  774. }
  775. int EESCHEMA_JOBS_HANDLER::JobSchErc( JOB* aJob )
  776. {
  777. JOB_SCH_ERC* ercJob = dynamic_cast<JOB_SCH_ERC*>( aJob );
  778. if( !ercJob )
  779. return CLI::EXIT_CODES::ERR_UNKNOWN;
  780. SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( ercJob->m_filename, SCH_IO_MGR::SCH_KICAD, true );
  781. if( sch == nullptr )
  782. {
  783. m_reporter->Report( _( "Failed to load schematic file\n" ), RPT_SEVERITY_ERROR );
  784. return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
  785. }
  786. sch->Prj().ApplyTextVars( aJob->GetVarOverrides() );
  787. if( ercJob->m_outputFile.IsEmpty() )
  788. {
  789. wxFileName fn = sch->GetFileName();
  790. fn.SetName( fn.GetName() );
  791. if( ercJob->m_format == JOB_SCH_ERC::OUTPUT_FORMAT::JSON )
  792. fn.SetExt( FILEEXT::JsonFileExtension );
  793. else
  794. fn.SetExt( FILEEXT::ReportFileExtension );
  795. ercJob->m_outputFile = fn.GetFullName();
  796. }
  797. EDA_UNITS units;
  798. switch( ercJob->m_units )
  799. {
  800. case JOB_SCH_ERC::UNITS::INCHES: units = EDA_UNITS::INCHES; break;
  801. case JOB_SCH_ERC::UNITS::MILS: units = EDA_UNITS::MILS; break;
  802. case JOB_SCH_ERC::UNITS::MILLIMETERS: units = EDA_UNITS::MILLIMETRES; break;
  803. default: units = EDA_UNITS::MILLIMETRES; break;
  804. }
  805. std::shared_ptr<SHEETLIST_ERC_ITEMS_PROVIDER> markersProvider =
  806. std::make_shared<SHEETLIST_ERC_ITEMS_PROVIDER>( sch );
  807. ERC_TESTER ercTester( sch );
  808. m_reporter->Report( _( "Running ERC...\n" ), RPT_SEVERITY_INFO );
  809. std::unique_ptr<DS_PROXY_VIEW_ITEM> drawingSheet( getDrawingSheetProxyView( sch ) );
  810. ercTester.RunTests( drawingSheet.get(), nullptr, m_kiway->KiFACE( KIWAY::FACE_CVPCB ),
  811. &sch->Prj(), m_progressReporter );
  812. markersProvider->SetSeverities( ercJob->m_severity );
  813. m_reporter->Report( wxString::Format( _( "Found %d violations\n" ),
  814. markersProvider->GetCount() ),
  815. RPT_SEVERITY_INFO );
  816. ERC_REPORT reportWriter( sch, units );
  817. bool wroteReport = false;
  818. if( ercJob->m_format == JOB_SCH_ERC::OUTPUT_FORMAT::JSON )
  819. wroteReport = reportWriter.WriteJsonReport( ercJob->m_outputFile );
  820. else
  821. wroteReport = reportWriter.WriteTextReport( ercJob->m_outputFile );
  822. if( !wroteReport )
  823. {
  824. m_reporter->Report( wxString::Format( _( "Unable to save ERC report to %s\n" ),
  825. ercJob->m_outputFile ),
  826. RPT_SEVERITY_INFO );
  827. return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
  828. }
  829. m_reporter->Report( wxString::Format( _( "Saved ERC Report to %s\n" ), ercJob->m_outputFile ),
  830. RPT_SEVERITY_INFO );
  831. if( ercJob->m_exitCodeViolations )
  832. {
  833. if( markersProvider->GetCount() > 0 )
  834. return CLI::EXIT_CODES::ERR_RC_VIOLATIONS;
  835. }
  836. return CLI::EXIT_CODES::SUCCESS;
  837. }
  838. DS_PROXY_VIEW_ITEM* EESCHEMA_JOBS_HANDLER::getDrawingSheetProxyView( SCHEMATIC* aSch )
  839. {
  840. DS_PROXY_VIEW_ITEM* drawingSheet =
  841. new DS_PROXY_VIEW_ITEM( schIUScale, &aSch->RootScreen()->GetPageSettings(),
  842. &aSch->Prj(), &aSch->RootScreen()->GetTitleBlock(),
  843. aSch->GetProperties() );
  844. drawingSheet->SetPageNumber( TO_UTF8( aSch->RootScreen()->GetPageNumber() ) );
  845. drawingSheet->SetSheetCount( aSch->RootScreen()->GetPageCount() );
  846. drawingSheet->SetFileName( TO_UTF8( aSch->RootScreen()->GetFileName() ) );
  847. drawingSheet->SetColorLayer( LAYER_SCHEMATIC_DRAWINGSHEET );
  848. drawingSheet->SetPageBorderColorLayer( LAYER_SCHEMATIC_PAGE_LIMITS );
  849. drawingSheet->SetIsFirstPage( aSch->RootScreen()->GetVirtualPageNumber() == 1 );
  850. drawingSheet->SetSheetName( "" );
  851. drawingSheet->SetSheetPath( "" );
  852. return drawingSheet;
  853. }