KiCad PCB EDA Suite
Loading...
Searching...
No Matches
eeschema_jobs_handler.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2022 Mark Roszko <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
23#include <algorithm>
24#include <common.h>
25#include <pgm_base.h>
26#include <kiface_base.h>
27#include <kiway_player.h>
28#include <cli/exit_codes.h>
29#include <sch_plotter.h>
32#include <jobs/job_export_bom.h>
36#include <jobs/job_sch_erc.h>
37#include <jobs/job_sch_import.h>
40#include <downgrade_target.h>
42#include <downgrade_scan.h>
43#include <downgrade_copy.h>
44#include <bus_alias.h>
45#include <lib_symbol.h>
47#include <sch_sheet_path.h>
48#include <set>
49#include <wx/ffile.h>
53#include <schematic.h>
54#include <import_net_map.h>
55#include <schematic_settings.h>
56#include <sch_screen.h>
57#include <sch_sheet.h>
58#include <sch_sheet_path.h>
59#include <sch_commit.h>
61#include <save_project_utils.h>
62#include <tool/tool_manager.h>
63#include <project.h>
65#include <wx/dir.h>
66#include <wx/file.h>
67#include <memory>
68#include <connection_graph.h>
69#include "eeschema_helpers.h"
70#include <filename_resolver.h>
71#include <kiway.h>
72#include <sch_painter.h>
73#include <locale_io.h>
74#include <erc/erc.h>
75#include <erc/erc_report.h>
76#include <erc/erc_settings.h>
80#include <paths.h>
81#include <reporter.h>
82#include <scoped_set_reset.h>
83#include <string_utils.h>
84
86
87#include <sch_file_versions.h>
88#include <sch_io/sch_io.h>
90
91#include <netlist.h>
101
103
108#include <confirm.h>
109#include <project_sch.h>
110
112
113
115 JOB_DISPATCHER( aKiway ),
116 m_cliSchematic( nullptr )
117{
118 Register( "bom", std::bind( &EESCHEMA_JOBS_HANDLER::JobExportBom, this, std::placeholders::_1 ),
119 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
120 {
121 JOB_EXPORT_BOM* bomJob = dynamic_cast<JOB_EXPORT_BOM*>( job );
122
123 SCH_EDIT_FRAME* editFrame = static_cast<SCH_EDIT_FRAME*>( aKiway->Player( FRAME_SCH, false ) );
124
125 wxCHECK( bomJob && editFrame, false );
126
127 DIALOG_SYMBOL_FIELDS_TABLE dlg( editFrame, bomJob );
128
129 if( dlg.WasAborted() )
130 return false;
131
132 return dlg.ShowModal() == wxID_OK;
133 } );
134 Register( "pythonbom", std::bind( &EESCHEMA_JOBS_HANDLER::JobExportPythonBom, this, std::placeholders::_1 ),
135 []( JOB* job, wxWindow* aParent ) -> bool
136 {
137 return true;
138 } );
139 Register( "netlist", std::bind( &EESCHEMA_JOBS_HANDLER::JobExportNetlist, this, std::placeholders::_1 ),
140 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
141 {
142 JOB_EXPORT_SCH_NETLIST* netJob = dynamic_cast<JOB_EXPORT_SCH_NETLIST*>( job );
143
144 SCH_EDIT_FRAME* editFrame = static_cast<SCH_EDIT_FRAME*>( aKiway->Player( FRAME_SCH, false ) );
145
146 wxCHECK( netJob && editFrame, false );
147
148 DIALOG_EXPORT_NETLIST dlg( editFrame, aParent, netJob );
149 return dlg.ShowModal() == wxID_OK;
150 } );
151 Register( "plot", std::bind( &EESCHEMA_JOBS_HANDLER::JobExportPlot, this, std::placeholders::_1 ),
152 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
153 {
154 JOB_EXPORT_SCH_PLOT* plotJob = dynamic_cast<JOB_EXPORT_SCH_PLOT*>( job );
155
156 SCH_EDIT_FRAME* editFrame = static_cast<SCH_EDIT_FRAME*>( aKiway->Player( FRAME_SCH, false ) );
157
158 wxCHECK( plotJob && editFrame, false );
159
160 if( plotJob->m_plotFormat == SCH_PLOT_FORMAT::HPGL )
161 {
162 DisplayErrorMessage( editFrame,
163 _( "Plotting to HPGL is no longer supported as of KiCad 10.0." ) );
164 return false;
165 }
166
167 DIALOG_PLOT_SCHEMATIC dlg( editFrame, aParent, plotJob );
168 return dlg.ShowModal() == wxID_OK;
169 } );
170 Register( "symupgrade", std::bind( &EESCHEMA_JOBS_HANDLER::JobSymUpgrade, this, std::placeholders::_1 ),
171 []( JOB* job, wxWindow* aParent ) -> bool
172 {
173 return true;
174 } );
175 Register( "symsvg", std::bind( &EESCHEMA_JOBS_HANDLER::JobSymExportSvg, this, std::placeholders::_1 ),
176 []( JOB* job, wxWindow* aParent ) -> bool
177 {
178 return true;
179 } );
180 Register( "sch_diff", std::bind( &EESCHEMA_JOBS_HANDLER::JobSchDiff, this, std::placeholders::_1 ),
181 []( JOB* job, wxWindow* aParent ) -> bool
182 {
183 return true;
184 } );
185 Register( "sym_diff", std::bind( &EESCHEMA_JOBS_HANDLER::JobSymDiff, this, std::placeholders::_1 ),
186 []( JOB* job, wxWindow* aParent ) -> bool
187 {
188 return true;
189 } );
190 Register( "erc", std::bind( &EESCHEMA_JOBS_HANDLER::JobSchErc, this, std::placeholders::_1 ),
191 []( JOB* job, wxWindow* aParent ) -> bool
192 {
193 JOB_SCH_ERC* ercJob = dynamic_cast<JOB_SCH_ERC*>( job );
194
195 wxCHECK( ercJob, false );
196
197 DIALOG_ERC_JOB_CONFIG dlg( aParent, ercJob );
198 return dlg.ShowModal() == wxID_OK;
199 } );
200 Register( "upgrade", std::bind( &EESCHEMA_JOBS_HANDLER::JobUpgrade, this, std::placeholders::_1 ),
201 []( JOB* job, wxWindow* aParent ) -> bool
202 {
203 return true;
204 } );
205 Register( "downgrade", std::bind( &EESCHEMA_JOBS_HANDLER::JobDowngrade, this, std::placeholders::_1 ),
206 []( JOB* job, wxWindow* aParent ) -> bool
207 {
208 return true;
209 } );
210 Register( "sch_import", std::bind( &EESCHEMA_JOBS_HANDLER::JobImport, this, std::placeholders::_1 ),
211 []( JOB* job, wxWindow* aParent ) -> bool
212 {
213 return true;
214 } );
215}
216
217
219{
220 if( m_cliSchematic )
221 m_cliSchematic->SetProject( nullptr );
222
223 delete m_cliSchematic;
224 m_cliSchematic = nullptr;
226}
227
228
229SCHEMATIC* EESCHEMA_JOBS_HANDLER::getSchematic( const wxString& aPath, bool aRequireRoot )
230{
231 SCHEMATIC* sch = nullptr;
232
233 if( !Pgm().IsGUI() && Pgm().GetSettingsManager().IsProjectOpenNotDummy() )
234 {
236 wxString schPath = aPath;
237
238 if( schPath.IsEmpty() )
239 {
240 wxFileName path = project.GetProjectFullName();
242 path.MakeAbsolute();
243 schPath = path.GetFullPath();
244 }
245
246 if( m_cliSchematic && aRequireRoot && !m_cliSchematicRootValidated )
248
249 if( !m_cliSchematic )
250 {
252 schPath, true, false, &project, true, aRequireRoot ? m_reporter : nullptr );
253 m_cliSchematicRootValidated = aRequireRoot;
254 }
255
256 sch = m_cliSchematic;
257 }
258 else if( Pgm().IsGUI() && Pgm().GetSettingsManager().IsProjectOpen() )
259 {
260 SCH_EDIT_FRAME* editFrame = static_cast<SCH_EDIT_FRAME*>( m_kiway->Player( FRAME_SCH, false ) );
261
262 if( editFrame )
263 sch = &editFrame->Schematic();
264 }
265 else if( !aPath.IsEmpty() )
266 {
268 aPath, true, false, nullptr, true, aRequireRoot ? m_reporter : nullptr );
269 }
270
271 if( !sch )
272 m_reporter->Report( _( "Failed to load schematic\n" ), RPT_SEVERITY_ERROR );
273
274 return sch;
275}
276
277void EESCHEMA_JOBS_HANDLER::InitRenderSettings( SCH_RENDER_SETTINGS* aRenderSettings, const wxString& aTheme,
278 SCHEMATIC* aSch, const wxString& aDrawingSheetOverride )
279{
280 COLOR_SETTINGS* cs = ::GetColorSettings( aTheme );
281 aRenderSettings->LoadColors( cs );
282 aRenderSettings->m_ShowHiddenPins = false;
283 aRenderSettings->m_ShowHiddenFields = false;
284 aRenderSettings->m_ShowPinAltIcons = false;
285
286 aRenderSettings->SetDefaultPenWidth( aSch->Settings().m_DefaultLineWidth );
287 aRenderSettings->m_LabelSizeRatio = aSch->Settings().m_LabelSizeRatio;
288 aRenderSettings->m_TextOffsetRatio = aSch->Settings().m_TextOffsetRatio;
289 aRenderSettings->m_PinSymbolSize = aSch->Settings().m_PinSymbolSize;
290 aRenderSettings->m_ShowDNPMarkers = aSch->Settings().m_ShowDNPMarkers;
291
292 aRenderSettings->SetDashLengthRatio( aSch->Settings().m_DashedLineDashRatio );
293 aRenderSettings->SetGapLengthRatio( aSch->Settings().m_DashedLineGapRatio );
294
295 // Load the drawing sheet from the filename stored in BASE_SCREEN::m_DrawingSheetFileName.
296 // If empty, or not existing, the default drawing sheet is loaded.
297
298 auto loadSheet = [&]( const wxString& path ) -> bool
299 {
300 wxString msg;
301
302 if( !DS_DATA_MODEL::GetTheInstance().LoadFromName( path, wxGetCwd(), &aSch->Project(),
303 { aSch->GetEmbeddedFiles() }, &msg ) )
304 {
305 m_reporter->Report( wxString::Format( _( "Error loading drawing sheet '%s'." ), path ) + wxS( "\n" ) + msg
306 + wxS( "\n" ),
308 return false;
309 }
310
311 return true;
312 };
313
314 // try to load the override first
315 if( !aDrawingSheetOverride.IsEmpty() && loadSheet( aDrawingSheetOverride ) )
316 return;
317
318 if( aSch->Settings().m_SchDrawingSheetFileName == wxS( "empty.kicad_wks" ) )
319 {
321 return;
322 }
323
324 // no override or failed override continues here
325 loadSheet( aSch->Settings().m_SchDrawingSheetFileName );
326}
327
328
330{
331 JOB_EXPORT_SCH_PLOT* aPlotJob = dynamic_cast<JOB_EXPORT_SCH_PLOT*>( aJob );
332
333 wxCHECK( aPlotJob, CLI::EXIT_CODES::ERR_UNKNOWN );
334
335 if( aPlotJob->m_plotFormat == SCH_PLOT_FORMAT::HPGL )
336 {
337 m_reporter->Report( _( "Plotting to HPGL is no longer supported as of KiCad 10.0.\n" ), RPT_SEVERITY_ERROR );
339 }
340
341 SCHEMATIC* sch = getSchematic( aPlotJob->m_filename );
342
343 if( !sch )
345
346 aJob->SetTitleBlock( sch->RootScreen()->GetTitleBlock() );
347 sch->Project().ApplyTextVars( aJob->GetVarOverrides() );
348
349 // Determine the variant to use. The dialog edit path writes m_variant (the scalar),
350 // while the CLI path populates m_variantNames directly. Prefer the scalar so a
351 // dialog-edited selection always wins over a stale list left over from CLI input.
352 wxString variantName;
353
354 if( !aPlotJob->m_variant.IsEmpty() )
355 variantName = aPlotJob->m_variant;
356 else if( !aPlotJob->m_variantNames.empty() )
357 variantName = aPlotJob->m_variantNames.front();
358
359 if( !variantName.IsEmpty() && variantName != wxS( "all" ) )
360 sch->SetCurrentVariant( variantName );
361
362 std::unique_ptr<SCH_RENDER_SETTINGS> renderSettings = std::make_unique<SCH_RENDER_SETTINGS>();
363 InitRenderSettings( renderSettings.get(), aPlotJob->m_theme, sch, aPlotJob->m_drawingSheet );
364
365 wxString font = aPlotJob->m_defaultFont;
366
367 if( font.IsEmpty() )
368 {
370 font = cfg ? cfg->m_Appearance.default_font : wxString( KICAD_FONT_NAME );
371 }
372
373 renderSettings->SetDefaultFont( font );
374 renderSettings->SetMinPenWidth( aPlotJob->m_minPenWidth );
375
376 // Clear cached bounding boxes for all text items so they're recomputed with the correct
377 // default font. This is necessary because text bounding boxes may have been cached during
378 // schematic loading before the render settings (and thus default font) were configured.
379 SCH_SCREENS screens( sch->Root() );
380
381 for( SCH_SCREEN* screen = screens.GetFirst(); screen; screen = screens.GetNext() )
382 {
383 for( SCH_ITEM* item : screen->Items() )
384 item->ClearCaches();
385
386 for( const auto& [libItemName, libSymbol] : screen->GetLibSymbols() )
387 libSymbol->ClearCaches();
388 }
389
390 std::unique_ptr<SCH_PLOTTER> schPlotter = std::make_unique<SCH_PLOTTER>( sch );
391
393
394 switch( aPlotJob->m_plotFormat )
395 {
396 case SCH_PLOT_FORMAT::DXF: format = PLOT_FORMAT::DXF; break;
397 case SCH_PLOT_FORMAT::PDF: format = PLOT_FORMAT::PDF; break;
398 case SCH_PLOT_FORMAT::SVG: format = PLOT_FORMAT::SVG; break;
399 case SCH_PLOT_FORMAT::POST: format = PLOT_FORMAT::POST; break;
400 case SCH_PLOT_FORMAT::PNG: format = PLOT_FORMAT::PNG; break;
401 case SCH_PLOT_FORMAT::HPGL: /* no longer supported */ break;
402 }
403
404 int pageSizeSelect = PageFormatReq::PAGE_SIZE_AUTO;
405
406 switch( aPlotJob->m_pageSizeSelect )
407 {
408 case JOB_PAGE_SIZE::PAGE_SIZE_A: pageSizeSelect = PageFormatReq::PAGE_SIZE_A; break;
409 case JOB_PAGE_SIZE::PAGE_SIZE_A4: pageSizeSelect = PageFormatReq::PAGE_SIZE_A4; break;
411 }
412
413 if( !aPlotJob->GetOutputPathIsDirectory() && aPlotJob->GetConfiguredOutputPath().IsEmpty() )
414 {
415 wxFileName fn = sch->GetFileName();
416 fn.SetName( fn.GetName() );
417 fn.SetExt( GetDefaultPlotExtension( format ) );
418
419 aPlotJob->SetConfiguredOutputPath( fn.GetFullName() );
420 }
421
422 wxString outPath = aPlotJob->GetFullOutputPath( &sch->Project() );
423
424 if( !PATHS::EnsurePathExists( outPath, !aPlotJob->GetOutputPathIsDirectory() ) )
425 {
426 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
428 }
429
430 SCH_PLOT_OPTS plotOpts;
431 plotOpts.m_blackAndWhite = aPlotJob->m_blackAndWhite;
432 plotOpts.m_PDFPropertyPopups = aPlotJob->m_PDFPropertyPopups;
434 plotOpts.m_PDFMetadata = aPlotJob->m_PDFMetadata;
435
436 if( aPlotJob->GetOutputPathIsDirectory() )
437 {
438 plotOpts.m_outputDirectory = outPath;
439 plotOpts.m_outputFile = wxEmptyString;
440 }
441 else
442 {
443 plotOpts.m_outputDirectory = wxEmptyString;
444 plotOpts.m_outputFile = outPath;
445 }
446
447 plotOpts.m_pageSizeSelect = pageSizeSelect;
448 plotOpts.m_plotAll = aPlotJob->m_plotAll;
449 plotOpts.m_plotDrawingSheet = aPlotJob->m_plotDrawingSheet;
450 plotOpts.m_plotPages = aPlotJob->m_plotPages;
451
452 if( !aPlotJob->m_sheetPath.IsEmpty() )
453 plotOpts.m_sheetPath = sch->Hierarchy().GetSheetPathByKIIDPath( KIID_PATH( aPlotJob->m_sheetPath ) );
454
455 plotOpts.m_theme = aPlotJob->m_theme;
456 plotOpts.m_useBackgroundColor = aPlotJob->m_useBackgroundColor;
457 plotOpts.m_plotHopOver = aPlotJob->m_show_hop_over;
458
459 if( !variantName.IsEmpty() )
460 plotOpts.m_variant = variantName;
461
462 // Always export dxf in mm by kicad-cli (similar to Pcbnew)
464
465 if( aPlotJob->m_plotFormat == SCH_PLOT_FORMAT::PNG )
466 {
467 JOB_EXPORT_SCH_PLOT_PNG* pngJob = static_cast<JOB_EXPORT_SCH_PLOT_PNG*>( aPlotJob );
468 plotOpts.m_pngDPI = pngJob->m_dpi;
469 plotOpts.m_pngAntialias = pngJob->m_antialias;
470 }
471
472 schPlotter->Plot( format, plotOpts, renderSettings.get(), m_reporter );
473
474 if( m_reporter->HasMessageOfSeverity( RPT_SEVERITY_ERROR ) )
476
477 for( const wxString& outputPath : schPlotter->GetOutputFilePaths() )
478 aJob->AddOutput( outputPath );
479
480 return CLI::EXIT_CODES::OK;
481}
482
483
485{
486 JOB_EXPORT_SCH_NETLIST* aNetJob = dynamic_cast<JOB_EXPORT_SCH_NETLIST*>( aJob );
487
488 wxCHECK( aNetJob, CLI::EXIT_CODES::ERR_UNKNOWN );
489
490 SCHEMATIC* sch = getSchematic( aNetJob->m_filename );
491
492 if( !sch )
494
495 aJob->SetTitleBlock( sch->RootScreen()->GetTitleBlock() );
496 sch->Project().ApplyTextVars( aJob->GetVarOverrides() );
497
498 // Apply variant if specified
499 if( !aNetJob->m_variantNames.empty() )
500 {
501 // For netlist export, we use the first variant name from the set
502 wxString variantName = *aNetJob->m_variantNames.begin();
503
504 if( variantName != wxS( "all" ) )
505 sch->SetCurrentVariant( variantName );
506 }
507
508 // Annotation warning check
509 SCH_REFERENCE_LIST referenceList;
510 sch->Hierarchy().GetSymbols( referenceList, SYMBOL_FILTER_NON_POWER );
511
512 if( referenceList.GetCount() > 0 )
513 {
514 if( referenceList.CheckAnnotation(
515 []( ERCE_T, const wxString&, SCH_REFERENCE*, SCH_REFERENCE* )
516 {
517 // We're only interested in the end result -- either errors or not
518 } )
519 > 0 )
520 {
521 m_reporter->Report( _( "Warning: schematic has annotation errors, please use the "
522 "schematic editor to fix them\n" ),
524 }
525 }
526
527 // Test duplicate sheet names:
528 ERC_TESTER erc( sch );
529
530 if( erc.TestDuplicateSheetNames( false ) > 0 )
531 m_reporter->Report( _( "Warning: duplicate sheet names.\n" ), RPT_SEVERITY_WARNING );
532
533 std::unique_ptr<NETLIST_EXPORTER_BASE> helper;
534 unsigned netlistOption = 0;
535
536 wxString fileExt;
537
538 switch( aNetJob->format )
539 {
542 helper = std::make_unique<NETLIST_EXPORTER_KICAD>( sch, m_kiway );
543 break;
544
547 helper = std::make_unique<NETLIST_EXPORTER_ORCADPCB2>( sch, m_kiway );
548 break;
549
552 helper = std::make_unique<NETLIST_EXPORTER_CADSTAR>( sch, m_kiway );
553 break;
554
558 helper = std::make_unique<NETLIST_EXPORTER_SPICE>( sch, m_kiway );
559 break;
560
563 helper = std::make_unique<NETLIST_EXPORTER_SPICE_MODEL>( sch, m_kiway );
564 break;
565
567 fileExt = wxS( "xml" );
568 helper = std::make_unique<NETLIST_EXPORTER_XML>( sch, m_kiway );
569 break;
570
572 fileExt = wxS( "asc" );
573 helper = std::make_unique<NETLIST_EXPORTER_PADS>( sch, m_kiway );
574 break;
575
577 fileExt = wxS( "txt" );
578 helper = std::make_unique<NETLIST_EXPORTER_ALLEGRO>( sch, m_kiway );
579 break;
580
581 default:
582 m_reporter->Report( _( "Unknown netlist format.\n" ), RPT_SEVERITY_ERROR );
584 }
585
586 if( aNetJob->GetConfiguredOutputPath().IsEmpty() )
587 {
588 wxFileName fn = sch->GetFileName();
589 fn.SetName( fn.GetName() );
590 fn.SetExt( fileExt );
591
592 aNetJob->SetConfiguredOutputPath( fn.GetFullName() );
593 }
594
595 wxString outPath = aNetJob->GetFullOutputPath( &sch->Project() );
596
597 if( !PATHS::EnsurePathExists( outPath, true ) )
598 {
599 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
601 }
602
603 bool res = helper->WriteNetlist( outPath, netlistOption, *m_reporter );
604
605 if( !res )
607
608 aJob->AddOutput( outPath );
609
610 return CLI::EXIT_CODES::OK;
611}
612
613
615{
616 JOB_EXPORT_BOM* aBomJob = dynamic_cast<JOB_EXPORT_BOM*>( aJob );
617
618 wxCHECK( aBomJob, CLI::EXIT_CODES::ERR_UNKNOWN );
619
620 SCHEMATIC* sch = getSchematic( aBomJob->m_filename );
621
622 if( !sch )
624
625 aJob->SetTitleBlock( sch->RootScreen()->GetTitleBlock() );
626 sch->Project().ApplyTextVars( aJob->GetVarOverrides() );
627
628 wxString currentVariant = aBomJob->GetSelectedVariant();
629
630 if( !currentVariant.IsEmpty() && currentVariant != wxS( "all" ) )
631 sch->SetCurrentVariant( currentVariant );
632
633 // Annotation warning check
634 SCH_REFERENCE_LIST referenceList;
635 sch->Hierarchy().GetSymbols( referenceList, SYMBOL_FILTER_NON_POWER, false );
636
637 if( referenceList.GetCount() > 0 )
638 {
639 SCH_REFERENCE_LIST copy = referenceList;
640
641 // Check annotation splits references...
642 if( copy.CheckAnnotation(
643 []( ERCE_T, const wxString&, SCH_REFERENCE*, SCH_REFERENCE* )
644 {
645 // We're only interested in the end result -- either errors or not
646 } )
647 > 0 )
648 {
649 m_reporter->Report( _( "Warning: schematic has annotation errors, please use the schematic "
650 "editor to fix them\n" ),
652 }
653 }
654
655 // Test duplicate sheet names:
656 ERC_TESTER erc( sch );
657
658 if( erc.TestDuplicateSheetNames( false ) > 0 )
659 m_reporter->Report( _( "Warning: duplicate sheet names.\n" ), RPT_SEVERITY_WARNING );
660
661 // Build our data model
662 SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL dataModel( referenceList );
663 dataModel.SetCurrentVariant( currentVariant );
664
665 // Mandatory fields first
666 for( FIELD_T fieldId : MANDATORY_FIELDS )
667 dataModel.AddColumn( GetDefaultFieldName( fieldId, UNTRANSLATED ),
668 GetDefaultFieldName( fieldId, TRANSLATED ), false );
669
670 // Generated/virtual fields (e.g. ${QUANTITY}, ${ITEM_NUMBER}) present only in the fields table
673 false );
676 false );
677
678 // Attribute fields (boolean flags on symbols)
679 dataModel.AddColumn( wxS( "${DNP}" ), GetGeneratedFieldDisplayName( wxS( "${DNP}" ) ), false );
680 dataModel.AddColumn( wxS( "${EXCLUDE_FROM_BOM}" ), GetGeneratedFieldDisplayName( wxS( "${EXCLUDE_FROM_BOM}" ) ),
681 false );
682 dataModel.AddColumn( wxS( "${EXCLUDE_FROM_BOARD}" ), GetGeneratedFieldDisplayName( wxS( "${EXCLUDE_FROM_BOARD}" ) ),
683 false );
684 dataModel.AddColumn( wxS( "${EXCLUDE_FROM_SIM}" ), GetGeneratedFieldDisplayName( wxS( "${EXCLUDE_FROM_SIM}" ) ),
685 false );
686
687 // User field names in symbols second
688 std::set<wxString> userFieldNames;
689
690 for( size_t i = 0; i < referenceList.GetCount(); ++i )
691 {
692 SCH_SYMBOL* symbol = referenceList[i].GetSymbol();
693
694 for( SCH_FIELD& field : symbol->GetFields() )
695 {
696 if( !field.IsMandatory() && !field.IsPrivate() )
697 userFieldNames.insert( field.GetName() );
698 }
699 }
700
701 for( const wxString& fieldName : userFieldNames )
702 dataModel.AddColumn( fieldName, GetGeneratedFieldDisplayName( fieldName ), true );
703
704 // Add any templateFieldNames which aren't already present in the userFieldNames
705 for( const TEMPLATE_FIELDNAME& templateFieldname :
707 {
708 if( userFieldNames.count( templateFieldname.m_Name ) == 0 )
709 {
710 dataModel.AddColumn( templateFieldname.m_Name, GetGeneratedFieldDisplayName( templateFieldname.m_Name ),
711 false );
712 }
713 }
714
715 BOM_PRESET preset;
716
717 // Load a preset if one is specified
718 if( !aBomJob->m_bomPresetName.IsEmpty() )
719 {
720 // Find the preset
721 std::optional<BOM_PRESET> schPreset;
722
723 for( const BOM_PRESET& p : BOM_PRESET::BuiltInPresets() )
724 {
725 if( p.name == aBomJob->m_bomPresetName )
726 {
727 schPreset = p;
728 break;
729 }
730 }
731
732 for( const BOM_PRESET& p : sch->Settings().m_BomPresets )
733 {
734 if( p.name == aBomJob->m_bomPresetName )
735 {
736 schPreset = p;
737 break;
738 }
739 }
740
741 if( !schPreset )
742 {
743 m_reporter->Report(
744 wxString::Format( _( "BOM preset '%s' not found" ) + wxS( "\n" ), aBomJob->m_bomPresetName ),
746
748 }
749
750 preset = *schPreset;
751 }
752 else
753 {
754 // Normalize field names so that bare generated-field tokens (e.g. "QUANTITY") are
755 // accepted alongside the canonical "${QUANTITY}" form. Shell expansion of ${VAR}
756 // inside double quotes silently produces an empty string, so this also guards against
757 // that common CLI pitfall.
758 auto normalizeFieldName = [&dataModel]( const wxString& aName ) -> wxString
759 {
760 if( aName.IsEmpty() )
761 return wxEmptyString;
762
763 if( IsGeneratedField( aName ) )
764 return aName;
765
766 wxString wrapped = wxS( "${" ) + aName + wxS( "}" );
767
768 if( IsGeneratedField( wrapped ) && dataModel.GetFieldNameCol( wrapped ) != -1 )
769 return wrapped;
770
771 return aName;
772 };
773
774 size_t i = 0;
775
776 for( const wxString& rawFieldName : aBomJob->m_fieldsOrdered )
777 {
778 wxString fieldName = normalizeFieldName( rawFieldName );
779
780 if( fieldName.IsEmpty() )
781 {
782 i++;
783 continue;
784 }
785
786 // Handle wildcard. We allow the wildcard anywhere in the list, but it needs to respect
787 // fields that come before and after the wildcard.
788 if( fieldName == wxS( "*" ) )
789 {
790 for( const BOM_FIELD& modelField : dataModel.GetFieldsOrdered() )
791 {
792 struct BOM_FIELD field;
793
794 field.name = modelField.name;
795 field.show = true;
796 field.groupBy = false;
797 field.label = field.name;
798
799 bool fieldAlreadyPresent = false;
800
801 for( BOM_FIELD& presetField : preset.fieldsOrdered )
802 {
803 if( presetField.name == field.name )
804 {
805 fieldAlreadyPresent = true;
806 break;
807 }
808 }
809
810 bool fieldLaterInList = false;
811
812 for( const wxString& fieldInList : aBomJob->m_fieldsOrdered )
813 {
814 if( normalizeFieldName( fieldInList ) == field.name )
815 {
816 fieldLaterInList = true;
817 break;
818 }
819 }
820
821 if( !fieldAlreadyPresent && !fieldLaterInList )
822 preset.fieldsOrdered.emplace_back( field );
823 }
824
825 continue;
826 }
827
828 struct BOM_FIELD field;
829
830 field.name = fieldName;
831 field.show = !fieldName.StartsWith( wxT( "__" ), &field.name );
832 field.name = normalizeFieldName( field.name );
833
834 if( ( aBomJob->m_fieldsLabels.size() > i ) && !aBomJob->m_fieldsLabels[i].IsEmpty() )
835 field.label = aBomJob->m_fieldsLabels[i];
836 else if( IsGeneratedField( field.name ) )
837 field.label = GetGeneratedFieldDisplayName( field.name );
838 else
839 field.label = field.name;
840
841 preset.fieldsOrdered.emplace_back( field );
842 i++;
843 }
844
845 for( const wxString& rawFieldName : aBomJob->m_fieldsGroupBy )
846 {
847 wxString fieldName = rawFieldName;
848 fieldName.StartsWith( wxT( "__" ), &fieldName );
849 fieldName = normalizeFieldName( fieldName );
850
851 if( fieldName.IsEmpty() )
852 continue;
853
854 bool fieldAlreadyPresent = false;
855
856 for( BOM_FIELD& field : preset.fieldsOrdered )
857 {
858 if( field.name == fieldName )
859 {
860 field.groupBy = true;
861 fieldAlreadyPresent = true;
862 break;
863 }
864 }
865
866 if( !fieldAlreadyPresent )
867 preset.fieldsOrdered.push_back( { fieldName, GetGeneratedFieldDisplayName( fieldName ), false, true } );
868 }
869
870 preset.sortAsc = aBomJob->m_sortAsc;
871 preset.sortField = normalizeFieldName( aBomJob->m_sortField );
872 preset.filterString = aBomJob->m_filterString;
873 preset.filterScope = aBomJob->m_filterScope;
874 preset.groupSymbols = aBomJob->m_groupSymbols;
875 preset.excludeDNP = aBomJob->m_excludeDNP;
876 }
877
878 BOM_FMT_PRESET fmt;
879
880 // Load a format preset if one is specified
881 if( !aBomJob->m_bomFmtPresetName.IsEmpty() )
882 {
883 std::optional<BOM_FMT_PRESET> schFmtPreset;
884
886 {
887 if( p.name == aBomJob->m_bomFmtPresetName )
888 {
889 schFmtPreset = p;
890 break;
891 }
892 }
893
894 for( const BOM_FMT_PRESET& p : sch->Settings().m_BomFmtPresets )
895 {
896 if( p.name == aBomJob->m_bomFmtPresetName )
897 {
898 schFmtPreset = p;
899 break;
900 }
901 }
902
903 if( !schFmtPreset )
904 {
905 m_reporter->Report( wxString::Format( _( "BOM format preset '%s' not found" ) + wxS( "\n" ),
906 aBomJob->m_bomFmtPresetName ),
908
910 }
911
912 fmt = *schFmtPreset;
913 }
914 else
915 {
916 fmt.fieldDelimiter = aBomJob->m_fieldDelimiter;
917 fmt.stringDelimiter = aBomJob->m_stringDelimiter;
918 fmt.refDelimiter = aBomJob->m_refDelimiter;
920 fmt.keepTabs = aBomJob->m_keepTabs;
921 fmt.keepLineBreaks = aBomJob->m_keepLineBreaks;
923 }
924
925 if( aBomJob->GetConfiguredOutputPath().IsEmpty() )
926 {
927 wxFileName fn = sch->GetFileName();
928 fn.SetName( fn.GetName() );
929 fn.SetExt( FILEEXT::CsvFileExtension );
930
931 aBomJob->SetConfiguredOutputPath( fn.GetFullName() );
932 }
933
934 wxString configuredPath = aBomJob->GetConfiguredOutputPath();
935 bool hasVariantPlaceholder = configuredPath.Contains( wxS( "${VARIANT}" ) );
936
937 // Determine which variants to process
938 std::vector<wxString> variantsToProcess;
939
940 if( aBomJob->m_variantNames.size() > 1 && hasVariantPlaceholder )
941 {
942 variantsToProcess = aBomJob->m_variantNames;
943 }
944 else
945 {
946 variantsToProcess.push_back( currentVariant );
947 }
948
949 for( const wxString& variantName : variantsToProcess )
950 {
951 std::vector<wxString> singleVariant = { variantName };
952 dataModel.SetVariantNames( singleVariant );
953 dataModel.SetCurrentVariant( variantName );
954 dataModel.UpdateReferences( dataModel.GetReferenceList() );
955 dataModel.ApplyBomPreset( preset );
956
957 wxString outPath;
958
959 if( hasVariantPlaceholder )
960 {
961 wxString variantPath = configuredPath;
962 variantPath.Replace( wxS( "${VARIANT}" ), variantName );
963 aBomJob->SetConfiguredOutputPath( variantPath );
964 outPath = aBomJob->GetFullOutputPath( &sch->Project() );
965 aBomJob->SetConfiguredOutputPath( configuredPath );
966 }
967 else
968 {
969 outPath = aBomJob->GetFullOutputPath( &sch->Project() );
970 }
971
972 if( !PATHS::EnsurePathExists( outPath, true ) )
973 {
974 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
976 }
977
978 wxFile f;
979
980 if( !f.Open( outPath, wxFile::write ) )
981 {
982 m_reporter->Report( wxString::Format( _( "Unable to open destination '%s'" ), outPath ),
984
986 }
987
988 bool res = f.Write( dataModel.Export( fmt ) );
989
990 if( !res )
992
993 aJob->AddOutput( outPath );
994
995 m_reporter->Report( wxString::Format( _( "Wrote bill of materials to '%s'." ), outPath ), RPT_SEVERITY_ACTION );
996 }
997
998 return CLI::EXIT_CODES::OK;
999}
1000
1001
1003{
1004 JOB_EXPORT_SCH_PYTHONBOM* aNetJob = dynamic_cast<JOB_EXPORT_SCH_PYTHONBOM*>( aJob );
1005
1006 wxCHECK( aNetJob, CLI::EXIT_CODES::ERR_UNKNOWN );
1007
1008 SCHEMATIC* sch = getSchematic( aNetJob->m_filename );
1009
1010 if( !sch )
1012
1013 aJob->SetTitleBlock( sch->RootScreen()->GetTitleBlock() );
1014 sch->Project().ApplyTextVars( aJob->GetVarOverrides() );
1015
1016 // Annotation warning check
1017 SCH_REFERENCE_LIST referenceList;
1018 sch->Hierarchy().GetSymbols( referenceList, SYMBOL_FILTER_NON_POWER );
1019
1020 if( referenceList.GetCount() > 0 )
1021 {
1022 if( referenceList.CheckAnnotation(
1023 []( ERCE_T, const wxString&, SCH_REFERENCE*, SCH_REFERENCE* )
1024 {
1025 // We're only interested in the end result -- either errors or not
1026 } )
1027 > 0 )
1028 {
1029 m_reporter->Report( _( "Warning: schematic has annotation errors, please use the "
1030 "schematic editor to fix them\n" ),
1032 }
1033 }
1034
1035 // Test duplicate sheet names:
1036 ERC_TESTER erc( sch );
1037
1038 if( erc.TestDuplicateSheetNames( false ) > 0 )
1039 m_reporter->Report( _( "Warning: duplicate sheet names.\n" ), RPT_SEVERITY_WARNING );
1040
1041 std::unique_ptr<NETLIST_EXPORTER_XML> xmlNetlist = std::make_unique<NETLIST_EXPORTER_XML>( sch, m_kiway );
1042
1043 if( aNetJob->GetConfiguredOutputPath().IsEmpty() )
1044 {
1045 wxFileName fn = sch->GetFileName();
1046 fn.SetName( fn.GetName() + "-bom" );
1047 fn.SetExt( FILEEXT::XmlFileExtension );
1048
1049 aNetJob->SetConfiguredOutputPath( fn.GetFullName() );
1050 }
1051
1052 wxString outPath = aNetJob->GetFullOutputPath( &sch->Project() );
1053
1054 if( !PATHS::EnsurePathExists( outPath, true ) )
1055 {
1056 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1058 }
1059
1060 bool res = xmlNetlist->WriteNetlist( outPath, GNL_OPT_BOM, *m_reporter );
1061
1062 if( !res )
1064
1065 aJob->AddOutput( outPath );
1066
1067 m_reporter->Report( wxString::Format( _( "Wrote bill of materials to '%s'." ), outPath ), RPT_SEVERITY_ACTION );
1068
1069 return CLI::EXIT_CODES::OK;
1070}
1071
1072
1074 LIB_SYMBOL* symbol )
1075{
1076 wxCHECK( symbol, CLI::EXIT_CODES::ERR_UNKNOWN );
1077
1078 std::shared_ptr<LIB_SYMBOL> parent;
1079 LIB_SYMBOL* symbolToPlot = symbol;
1080
1081 // if the symbol is an alias, then the draw items are stored in the root symbol
1082 if( symbol->IsDerived() )
1083 {
1084 parent = symbol->GetRootSymbol();
1085
1086 wxCHECK( parent, CLI::EXIT_CODES::ERR_UNKNOWN );
1087
1088 symbolToPlot = parent.get();
1089 }
1090
1091 // iterate from unit 1, unit 0 would be "all units" which we don't want
1092 for( int unit = 1; unit < symbol->GetUnitCount() + 1; unit++ )
1093 {
1094 for( int bodyStyle = 1; bodyStyle <= symbol->GetBodyStyleCount(); ++bodyStyle )
1095 {
1096 wxString filename;
1097 wxFileName fn;
1098
1099 fn.SetPath( aSvgJob->m_outputDirectory );
1100 fn.SetExt( FILEEXT::SVGFileExtension );
1101
1102 filename = symbol->GetName();
1103
1104 for( wxChar c : wxFileName::GetForbiddenChars( wxPATH_DOS ) )
1105 filename.Replace( c, ' ' );
1106
1107 // Even single units get a unit number in the filename. This simplifies the
1108 // handling of the files as they have a uniform pattern.
1109 // Also avoids aliasing 'sym', unit 2 and 'sym_unit2', unit 1 to the same file.
1110 filename += wxString::Format( "_unit%d", unit );
1111
1112 if( symbol->HasDeMorganBodyStyles() )
1113 {
1114 if( bodyStyle == 2 )
1115 filename += wxS( "_demorgan" );
1116 }
1117 else if( bodyStyle <= (int) symbol->GetBodyStyleNames().size() )
1118 {
1119 filename += wxS( "_" ) + symbol->GetBodyStyleNames()[bodyStyle - 1].Lower();
1120 }
1121
1122 fn.SetName( filename );
1123 m_reporter->Report( wxString::Format( _( "Plotting symbol '%s' unit %d to '%s'\n" ), symbol->GetName(),
1124 unit, fn.GetFullPath() ),
1126
1127 BOX2I symbolBB = GetSymbolPlotBBox( *symbol, unit, bodyStyle, aSvgJob->m_includeHiddenFields );
1128
1129 if( !PlotSymbolToSVG( *symbolToPlot, *symbol, unit, bodyStyle, symbolBB, *aRenderSettings,
1130 aSvgJob->m_blackAndWhite, fn.GetFullPath(), m_reporter ) )
1131 {
1133 }
1134 }
1135 }
1136
1137 if( m_reporter->HasMessageOfSeverity( RPT_SEVERITY_ERROR ) )
1139
1140 return CLI::EXIT_CODES::OK;
1141}
1142
1143
1145{
1146 JOB_SYM_EXPORT_SVG* svgJob = dynamic_cast<JOB_SYM_EXPORT_SVG*>( aJob );
1147
1148 wxCHECK( svgJob, CLI::EXIT_CODES::ERR_UNKNOWN );
1149
1150 wxFileName fn( svgJob->m_libraryPath );
1151 fn.MakeAbsolute();
1152
1153 // When the input is a single symbol file we restrict plotting to the symbols defined in
1154 // that file. Stays empty (no restriction) when the input is a whole library.
1155 wxString singleFileFilter;
1156
1157 auto schLibrary = std::make_unique<SCH_IO_KICAD_SEXPR_LIB_CACHE>( fn.GetFullPath() );
1158
1159 try
1160 {
1161 schLibrary->Load();
1162 }
1163 catch( ... )
1164 {
1165 // A single file holding a derived symbol whose parent is in a sibling file cannot load
1166 // alone. Retry against the enclosing directory, then plot only this file's symbols.
1167 bool recovered = false;
1168
1169 if( !fn.IsDir() && wxDir::Exists( fn.GetPath() ) )
1170 {
1171 try
1172 {
1173 schLibrary = std::make_unique<SCH_IO_KICAD_SEXPR_LIB_CACHE>( fn.GetPath() );
1174 schLibrary->Load();
1175 singleFileFilter = fn.GetFullPath();
1176 recovered = true;
1177 }
1178 catch( ... )
1179 {
1180 // Fall through to the generic load error below.
1181 }
1182 }
1183
1184 if( !recovered )
1185 {
1186 m_reporter->Report( _( "Unable to load library\n" ), RPT_SEVERITY_ERROR );
1188 }
1189 }
1190
1191 if( m_progressReporter )
1192 m_progressReporter->KeepRefreshing();
1193
1194 LIB_SYMBOL* symbol = nullptr;
1195
1196 if( !svgJob->m_symbol.IsEmpty() )
1197 {
1198 // See if the selected symbol exists
1199 symbol = schLibrary->GetSymbol( svgJob->m_symbol );
1200
1201 if( !symbol )
1202 {
1203 m_reporter->Report( _( "There is no symbol selected to save." ) + wxS( "\n" ), RPT_SEVERITY_ERROR );
1205 }
1206 }
1207
1208 if( !svgJob->m_outputDirectory.IsEmpty() && !wxDir::Exists( svgJob->m_outputDirectory ) )
1209 {
1210 if( !wxFileName::Mkdir( svgJob->m_outputDirectory ) )
1211 {
1212 m_reporter->Report( wxString::Format( _( "Unable to create output directory '%s'." ) + wxS( "\n" ),
1213 svgJob->m_outputDirectory ),
1216 }
1217 }
1218
1219 SCH_RENDER_SETTINGS renderSettings;
1221 renderSettings.LoadColors( cs );
1222 renderSettings.SetDefaultPenWidth( DEFAULT_LINE_WIDTH_MILS * schIUScale.IU_PER_MILS );
1223 renderSettings.m_ShowHiddenPins = svgJob->m_includeHiddenPins;
1224 renderSettings.m_ShowHiddenFields = svgJob->m_includeHiddenFields;
1225
1226 int exitCode = CLI::EXIT_CODES::OK;
1227
1228 if( symbol )
1229 {
1230 exitCode = doSymExportSvg( svgJob, &renderSettings, symbol );
1231 }
1232 else
1233 {
1234 // Just plot all the symbols we can
1235 const LIB_SYMBOL_MAP& libSymMap = schLibrary->GetSymbolMap();
1236 const std::map<wxString, wxString>& sourceFiles = schLibrary->GetSymbolSourceFiles();
1237 const wxFileName filterFile( singleFileFilter );
1238
1239 for( const auto& [name, libSymbol] : libSymMap )
1240 {
1241 // When a single file was requested, skip symbols that came from sibling files.
1242 if( !singleFileFilter.IsEmpty() )
1243 {
1244 auto srcIt = sourceFiles.find( name );
1245
1246 if( srcIt == sourceFiles.end() || !wxFileName( srcIt->second ).SameAs( filterFile ) )
1247 continue;
1248 }
1249
1250 if( m_progressReporter )
1251 {
1252 m_progressReporter->AdvancePhase( wxString::Format( _( "Exporting %s" ), name ) );
1253 m_progressReporter->KeepRefreshing();
1254 }
1255
1256 exitCode = doSymExportSvg( svgJob, &renderSettings, libSymbol );
1257
1258 if( exitCode != CLI::EXIT_CODES::OK )
1259 break;
1260 }
1261 }
1262
1263 return exitCode;
1264}
1265
1266
1268{
1269 JOB_SYM_UPGRADE* upgradeJob = dynamic_cast<JOB_SYM_UPGRADE*>( aJob );
1270
1271 wxCHECK( upgradeJob, CLI::EXIT_CODES::ERR_UNKNOWN );
1272
1273 wxFileName fn( upgradeJob->m_libraryPath );
1274 fn.MakeAbsolute();
1275
1276 SCH_IO_MGR::SCH_FILE_T fileType = SCH_IO_MGR::GuessPluginTypeFromLibPath( fn.GetFullPath() );
1277
1278 if( !upgradeJob->m_outputLibraryPath.IsEmpty() )
1279 {
1280 if( wxFile::Exists( upgradeJob->m_outputLibraryPath ) )
1281 {
1282 m_reporter->Report( _( "Output path must not conflict with existing path\n" ), RPT_SEVERITY_ERROR );
1283
1285 }
1286 }
1287 else if( fileType != SCH_IO_MGR::SCH_KICAD )
1288 {
1289 m_reporter->Report( _( "Output path must be specified to convert legacy and non-KiCad libraries\n" ),
1291
1293 }
1294
1295 if( fileType == SCH_IO_MGR::SCH_KICAD )
1296 {
1297 SCH_IO_KICAD_SEXPR_LIB_CACHE schLibrary( fn.GetFullPath() );
1298
1299 try
1300 {
1301 schLibrary.Load();
1302 }
1303 catch( ... )
1304 {
1305 m_reporter->Report( _( "Unable to load library\n" ), RPT_SEVERITY_ERROR );
1307 }
1308
1309 if( m_progressReporter )
1310 m_progressReporter->KeepRefreshing();
1311
1312 bool shouldSave =
1314
1315 if( shouldSave )
1316 {
1317 m_reporter->Report( _( "Saving symbol library in updated format\n" ), RPT_SEVERITY_ACTION );
1318
1319 try
1320 {
1321 if( !upgradeJob->m_outputLibraryPath.IsEmpty() )
1322 schLibrary.SetFileName( upgradeJob->m_outputLibraryPath );
1323
1324 schLibrary.SetModified();
1325 schLibrary.Save();
1326 }
1327 catch( ... )
1328 {
1329 m_reporter->Report( ( "Unable to save library\n" ), RPT_SEVERITY_ERROR );
1331 }
1332 }
1333 else
1334 {
1335 m_reporter->Report( _( "Symbol library was not updated\n" ), RPT_SEVERITY_ERROR );
1336 }
1337 }
1338 else
1339 {
1340 if( !SCH_IO_MGR::ConvertLibrary( nullptr, fn.GetAbsolutePath(), upgradeJob->m_outputLibraryPath, m_reporter ) )
1341 {
1342 m_reporter->Report( ( "Unable to convert library\n" ), RPT_SEVERITY_ERROR );
1344 }
1345 }
1346
1347 return CLI::EXIT_CODES::OK;
1348}
1349
1350
1352{
1353 JOB_SCH_ERC* ercJob = dynamic_cast<JOB_SCH_ERC*>( aJob );
1354
1355 wxCHECK( ercJob, CLI::EXIT_CODES::ERR_UNKNOWN );
1356
1357 SCHEMATIC* sch = getSchematic( ercJob->m_filename );
1358
1359 if( !sch )
1361
1362 aJob->SetTitleBlock( sch->RootScreen()->GetTitleBlock() );
1363 sch->Project().ApplyTextVars( aJob->GetVarOverrides() );
1364
1365 if( ercJob->GetConfiguredOutputPath().IsEmpty() )
1366 {
1367 wxFileName fn = sch->GetFileName();
1368 fn.SetName( fn.GetName() + wxS( "-erc" ) );
1369
1371 fn.SetExt( FILEEXT::JsonFileExtension );
1372 else
1373 fn.SetExt( FILEEXT::ReportFileExtension );
1374
1375 // Use a transient working path so an empty configured output filename isn't persisted
1376 // back into the jobset file. Mirrors the PCB DRC handler.
1377 ercJob->SetWorkingOutputPath( fn.GetFullName() );
1378 }
1379
1380 wxString outPath = ercJob->GetFullOutputPath( &sch->Project() );
1381
1382 if( !PATHS::EnsurePathExists( outPath, true ) )
1383 {
1384 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1386 }
1387
1388 EDA_UNITS units;
1389
1390 switch( ercJob->m_units )
1391 {
1392 case JOB_SCH_ERC::UNITS::INCH: units = EDA_UNITS::INCH; break;
1393 case JOB_SCH_ERC::UNITS::MILS: units = EDA_UNITS::MILS; break;
1394 case JOB_SCH_ERC::UNITS::MM: units = EDA_UNITS::MM; break;
1395 default: units = EDA_UNITS::MM; break;
1396 }
1397
1398 std::shared_ptr<SHEETLIST_ERC_ITEMS_PROVIDER> markersProvider =
1399 std::make_shared<SHEETLIST_ERC_ITEMS_PROVIDER>( sch );
1400
1401 // Running ERC requires libraries be loaded, so make sure they have been
1403 adapter->AsyncLoad();
1404 adapter->BlockUntilLoaded();
1405
1406 ERC_TESTER ercTester( sch );
1407
1408 std::unique_ptr<DS_PROXY_VIEW_ITEM> drawingSheet( getDrawingSheetProxyView( sch ) );
1409 SCH_EDIT_FRAME* editFrame = nullptr;
1410
1411 if( Pgm().IsGUI() )
1412 {
1413 editFrame = static_cast<SCH_EDIT_FRAME*>( m_kiway->Player( FRAME_SCH, false ) );
1414
1415 if( editFrame && &editFrame->Schematic() != sch )
1416 editFrame = nullptr;
1417 }
1418
1419 if( editFrame )
1420 editFrame->ClearErcMarkers();
1421
1422 ercTester.RunTests( drawingSheet.get(), editFrame, m_kiway->KiFACE( KIWAY::FACE_CVPCB ), &sch->Project(),
1424
1425 if( editFrame )
1426 editFrame->RefreshErcMarkers();
1427
1428 markersProvider->SetSeverities( ercJob->m_severity );
1429
1430 m_reporter->Report( wxString::Format( _( "Found %d violations\n" ), markersProvider->GetCount() ),
1432
1433 ERC_REPORT reportWriter( sch, units, markersProvider );
1434
1435 bool wroteReport = false;
1436
1438 wroteReport = reportWriter.WriteJsonReport( outPath );
1439 else
1440 wroteReport = reportWriter.WriteTextReport( outPath );
1441
1442 if( !wroteReport )
1443 {
1444 m_reporter->Report( wxString::Format( _( "Unable to save ERC report to %s\n" ), outPath ), RPT_SEVERITY_ERROR );
1446 }
1447
1448 m_reporter->Report( wxString::Format( _( "Saved ERC Report to %s\n" ), outPath ), RPT_SEVERITY_ACTION );
1449
1450 if( ercJob->m_exitCodeViolations )
1451 {
1452 if( markersProvider->GetCount() > 0 )
1454 }
1455
1457}
1458
1459
1461{
1462 JOB_SCH_UPGRADE* aUpgradeJob = dynamic_cast<JOB_SCH_UPGRADE*>( aJob );
1463
1464 if( aUpgradeJob == nullptr )
1466
1467 SCHEMATIC* sch = getSchematic( aUpgradeJob->m_filename, false );
1468
1469 if( !sch )
1471
1472 bool shouldSave = aUpgradeJob->m_force;
1473
1475 shouldSave = true;
1476
1477 if( !shouldSave )
1478 {
1479 m_reporter->Report( _( "Schematic file was not updated\n" ), RPT_SEVERITY_ERROR );
1481 }
1482
1483 // needs an absolute path
1484 wxFileName schPath( aUpgradeJob->m_filename );
1485 schPath.MakeAbsolute();
1486 const wxString schFullPath = schPath.GetFullPath();
1487
1488 try
1489 {
1490 IO_RELEASER<SCH_IO> pi( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
1491 SCH_SHEET* loadedSheet = pi->LoadSchematicFile( schFullPath, sch );
1492 pi->SaveSchematicFile( schFullPath, loadedSheet, sch );
1493 }
1494 catch( const IO_ERROR& ioe )
1495 {
1496 wxString msg =
1497 wxString::Format( _( "Error saving schematic file '%s'.\n%s" ), schFullPath, ioe.What().GetData() );
1498 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1500 }
1501
1502 m_reporter->Report( _( "Successfully saved schematic file using the latest format\n" ), RPT_SEVERITY_INFO );
1503
1505}
1506
1507
1508// Unloads a project that was only opened for the duration of a job.
1510{
1514
1516 {
1517 if( m_active )
1518 m_mgr.UnloadProject( m_project, false );
1519 }
1520};
1521
1522
1523// Classify library symbols so their losses appear in the report.
1524static void classifySymbolLibraries( const wxString& aDir, const DOWNGRADE_TARGET& aTarget,
1525 COMPATIBILITY_REPORT& aReport, bool aDropInsteadOfApproximate )
1526{
1527 wxArrayString files;
1528 wxDir::GetAllFiles( aDir, &files, wxT( "*.kicad_sym" ) );
1529
1530 for( const wxString& file : files )
1531 {
1532 if( IsDowngradeWorkingArtifact( file ) )
1533 continue;
1534
1535 try
1536 {
1538 std::vector<LIB_SYMBOL*> symbols;
1539 pi.EnumerateSymbolLib( symbols, file );
1540
1541 for( const LIB_SYMBOL* symbol : symbols )
1542 {
1543 aReport.Merge( ClassifyLibSymbolForDowngrade( symbol, aTarget, aDropInsteadOfApproximate ) );
1544 }
1545 }
1546 catch( const IO_ERROR& )
1547 {
1548 // A library that fails to load is caught later by the downgrade pass itself.
1549 }
1550 }
1551}
1552
1553
1554// Downgrade every .kicad_sym under the folder in place. Returns the first file the target could
1555// not open, or empty when all are safe.
1556static wxString downgradeSymbolLibraries( const wxString& aDir, const DOWNGRADE_TARGET& aTarget,
1557 bool aDropInsteadOfApproximate,
1558 DOWNGRADE_FILE_TRANSACTION* aTransaction = nullptr,
1559 wxString* aRecoveryError = nullptr )
1560{
1561 auto convert = [&]( const wxString& aFile, const wxString& aTmp ) -> DOWNGRADE_FILE_RESULT
1562 {
1564 std::vector<LIB_SYMBOL*> symbols;
1565 pi.EnumerateSymbolLib( symbols, aFile );
1566
1567 // The format wants parents before the symbols that inherit from them, so sort by depth.
1568 std::stable_sort( symbols.begin(), symbols.end(),
1569 []( const LIB_SYMBOL* a, const LIB_SYMBOL* b )
1570 {
1571 return a->GetInheritanceDepth() < b->GetInheritanceDepth();
1572 } );
1573
1574 for( LIB_SYMBOL* symbol : symbols )
1575 DowngradeLibSymbolInPlace( symbol, aTarget, aDropInsteadOfApproximate );
1576
1577 SaveSymbolLibraryForTarget( symbols, aTmp, aTarget );
1579 };
1580
1582 aDir, wxT( "*.kicad_sym" ), aTarget.m_symLibVersion,
1583 [&]( const wxString& aText )
1584 {
1585 return FindUnsupportedSchToken( aText, aTarget );
1586 },
1587 convert, aTransaction, aRecoveryError );
1588}
1589
1590
1592{
1593 JOB_SCH_DOWNGRADE* job = dynamic_cast<JOB_SCH_DOWNGRADE*>( aJob );
1594
1595 if( job == nullptr )
1597
1598 const DOWNGRADE_TARGET* target =
1599 job->m_target.IsEmpty() ? &GetDowngradeTargets().front() : FindDowngradeTarget( job->m_target );
1600
1601 if( target == nullptr )
1602 {
1603 m_reporter->Report( wxString::Format( _( "Unknown target version '%s'. Valid targets: %s\n" ), job->m_target,
1607 }
1608
1609 if( !job->m_libraryDir.IsEmpty() && !wxDirExists( job->m_libraryDir ) )
1610 {
1611 m_reporter->Report( wxString::Format( _( "The library folder '%s' does not exist.\n" ), job->m_libraryDir ),
1614 }
1615
1616 DOWNGRADE_OUTPUT_DIRECTORIES outputDirectories;
1617 wxString libraryWorkDir = job->m_libraryDir;
1618 wxString libraryCopyDir;
1619 wxString outputError;
1620
1621 if( !job->m_inPlace && !job->m_dryRun )
1622 {
1623 wxFileName output = wxFileName::DirName( job->m_outputDir );
1624
1625 if( job->m_outputDir.IsEmpty() || !output.MakeAbsolute()
1626 || !ValidateFreshDowngradeOutput( output.GetPath(), outputError ) )
1627 {
1628 if( outputError.IsEmpty() )
1629 outputError = _( "An output folder is required. Use --output." );
1630
1631 m_reporter->Report( outputError + wxS( "\n" ), RPT_SEVERITY_ERROR );
1633 }
1634
1635 job->m_outputDir = output.GetPath();
1636 }
1637
1638 if( !job->m_libraryDir.IsEmpty() )
1639 {
1640 if( !ValidateDowngradeLibrarySource( job->m_libraryDir, outputError ) )
1641 {
1642 m_reporter->Report( outputError + wxS( "\n" ), RPT_SEVERITY_ERROR );
1644 }
1645
1646 if( !job->m_inPlace && !job->m_dryRun )
1647 {
1648 libraryCopyDir = DowngradeLibraryCopyPath( job->m_libraryDir, job->m_outputDir );
1649
1650 if( !ValidateDowngradeLibraryCopy( job->m_libraryDir, libraryCopyDir, outputError ) )
1651 {
1652 m_reporter->Report( outputError + wxS( "\n" ), RPT_SEVERITY_ERROR );
1654 }
1655 }
1656 }
1657
1658 auto prepareOutput = [&]() -> bool
1659 {
1660 if( job->m_inPlace )
1661 return true;
1662
1663 if( !outputDirectories.Create( job->m_outputDir, true ) )
1664 {
1665 m_reporter->Report( _( "Could not create the schematic output folder.\n" ), RPT_SEVERITY_ERROR );
1666 return false;
1667 }
1668
1669 if( !libraryCopyDir.IsEmpty() )
1670 {
1671 if( !CopyDowngradeLibraryDirectory( job->m_libraryDir, libraryCopyDir, outputDirectories, outputError ) )
1672 {
1673 m_reporter->Report( outputError + wxS( "\n" ), RPT_SEVERITY_ERROR );
1674 return false;
1675 }
1676
1677 libraryWorkDir = libraryCopyDir;
1678 }
1679
1680 return true;
1681 };
1682
1683 auto downgradeLibraries = [&]( DOWNGRADE_FILE_TRANSACTION* aTransaction = nullptr ) -> int
1684 {
1685 if( job->m_dryRun || job->m_libraryDir.IsEmpty() )
1687
1688 try
1689 {
1690 wxString recoveryError;
1691 wxString badLib = downgradeSymbolLibraries( libraryWorkDir, *target, job->m_dropInsteadOfApproximate,
1692 aTransaction, &recoveryError );
1693
1694 if( !badLib.IsEmpty() )
1695 {
1696 m_reporter->Report( wxString::Format( _( "Cannot export: symbol library '%s' uses a feature %s "
1697 "cannot open.\n" ),
1698 badLib, target->m_name ),
1700
1701 if( !recoveryError.IsEmpty() )
1702 m_reporter->Report( recoveryError, RPT_SEVERITY_ERROR );
1703
1705 }
1706 }
1707 catch( const IO_ERROR& ioe )
1708 {
1709 m_reporter->Report(
1710 wxString::Format( _( "Error downgrading symbol libraries.\n%s" ), ioe.What().GetData() ),
1713 }
1714
1716 };
1717
1718 // No schematic to process. Do the libraries and stop.
1719 if( job->m_filename.IsEmpty() )
1720 {
1721 if( !job->m_variant.IsEmpty() )
1722 {
1723 m_reporter->Report( _( "Variants do not apply to a library downgrade.\n" ), RPT_SEVERITY_ERROR );
1725 }
1726
1727 if( !job->m_libraryDir.IsEmpty() )
1728 {
1729 COMPATIBILITY_REPORT report;
1731 job->m_report = report;
1732 job->m_report.Print( m_reporter );
1733
1734 if( !job->m_dryRun && report.IsBlocked() )
1735 {
1736 m_reporter->Report( _( "Cannot export: the libraries use features the target cannot represent.\n" ),
1739 }
1740
1741 if( !job->m_dryRun && report.IsLossy() && !job->m_force )
1742 {
1743 m_reporter->Report( _( "Export would lose data. Re-run with --force to export anyway.\n" ),
1746 }
1747 }
1748
1749 if( job->m_dryRun )
1751
1752 if( !prepareOutput() )
1754
1755 int result = downgradeLibraries();
1756
1758 outputDirectories.Commit();
1759
1760 return result;
1761 }
1762
1763 // The project and schematic loaders require absolute paths.
1764 wxFileName schInputFn( job->m_filename );
1765 schInputFn.MakeAbsolute();
1766 job->m_filename = schInputFn.GetFullPath();
1767
1768 // Load a private copy, never the live editor schematic. A transient project keeps it apart
1769 // from anything open in the GUI.
1771
1772 wxFileName projectFn( job->m_filename );
1773 projectFn.SetExt( FILEEXT::ProjectFileExtension );
1774
1775 PROJECT* projectPtr = mgr.GetProject( projectFn.GetFullPath() );
1776 bool createdTransientProject = false;
1777
1778 if( !projectPtr )
1779 {
1780 mgr.LoadProject( projectFn.GetFullPath(), false );
1781 projectPtr = mgr.GetProject( projectFn.GetFullPath() );
1782 createdTransientProject = ( projectPtr != nullptr );
1783 }
1784
1785 if( !projectPtr )
1786 {
1787 m_reporter->Report( _( "Could not establish a project for the schematic\n" ), RPT_SEVERITY_ERROR );
1789 }
1790
1791 TRANSIENT_PROJECT_GUARD transientProjectGuard{ mgr, projectPtr, createdTransientProject };
1792
1794
1795 // Binding a SCHEMATIC to a project reallocates that project's ERC and schematic settings. When
1796 // we borrowed a live project, snapshot the pointers so we can put them back. This relies on
1797 // ~SCHEMATIC not calling SetProject.
1798 PROJECT_FILE& projectFile = projectPtr->GetProjectFile();
1799
1800 // The settings loader repairs missing root filenames as if this were a renamed
1801 // template. That is unsafe during export: check the stored list before using the
1802 // repaired in-memory list, or a missing sibling can be replaced by the main sheet.
1803 if( auto storedRoots = projectFile.GetJson( "schematic.top_level_sheets" ); storedRoots && storedRoots->is_array() )
1804 {
1805 for( const auto& info : *storedRoots )
1806 {
1807 if( !info.contains( "filename" ) || !info["filename"].is_string() )
1808 continue;
1809
1810 const wxString filename = wxString::FromUTF8( info["filename"].get<std::string>() );
1811 wxFileName rootFile( filename );
1812 rootFile.MakeAbsolute( projectPtr->GetProjectPath() );
1813
1814 if( filename.IsEmpty() || !rootFile.FileExists() )
1815 {
1816 m_reporter->Report( wxString::Format( _( "Cannot export: top-level schematic '%s' is missing.\n" ),
1817 rootFile.GetFullPath() ),
1820 }
1821 }
1822 }
1823
1824 ERC_SETTINGS* savedErc = createdTransientProject ? nullptr : projectFile.m_ErcSettings;
1825 SCHEMATIC_SETTINGS* savedSch = createdTransientProject ? nullptr : projectFile.m_SchematicSettings;
1826
1827 std::unique_ptr<SCHEMATIC> owned = std::make_unique<SCHEMATIC>( projectPtr );
1828 owned->CreateDefaultScreens();
1829 SCHEMATIC* sch = owned.get();
1830
1831 struct SCH_PROJECT_GUARD
1832 {
1833 SCHEMATIC* m_sch;
1834 PROJECT* m_project;
1835 bool m_created;
1836 ERC_SETTINGS* m_savedErc;
1837 SCHEMATIC_SETTINGS* m_savedSch;
1838
1839 ~SCH_PROJECT_GUARD()
1840 {
1841 if( m_created )
1842 {
1843 m_sch->SetProject( nullptr );
1844 }
1845 else
1846 {
1847 // Drop the settings this schematic made and put the live project's own back.
1848 PROJECT_FILE& pf = m_project->GetProjectFile();
1849 delete pf.m_ErcSettings;
1850 delete pf.m_SchematicSettings;
1851 pf.m_ErcSettings = m_savedErc;
1852 pf.m_SchematicSettings = m_savedSch;
1853 }
1854 }
1855 } schProjectGuard{ sch, projectPtr, createdTransientProject, savedErc, savedSch };
1856
1857 try
1858 {
1859 IO_RELEASER<SCH_IO> pi( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
1860 SCH_SHEET* root = pi->LoadSchematicFile( job->m_filename, sch );
1861
1862 if( !root )
1863 {
1864 m_reporter->Report( _( "Failed to load schematic\n" ), RPT_SEVERITY_ERROR );
1866 }
1867
1868 // The loader collects sub-sheet errors instead of throwing. An incomplete hierarchy must
1869 // not be exported, or missing sheets would be written out empty.
1870 if( !pi->GetError().IsEmpty() )
1871 {
1872 m_reporter->Report( wxString::Format( _( "Cannot export: the schematic did not load "
1873 "completely.\n%s" ),
1874 pi->GetError() ),
1877 }
1878
1879 // The loader does not install the hierarchy. Without this the sheet walk below sees only
1880 // the empty default screens and the export silently does nothing.
1881 std::vector<SCH_SHEET*> topLevelSheets = sch->GetTopLevelSheets();
1882 bool rootIsTopLevel = std::find( topLevelSheets.begin(), topLevelSheets.end(), root ) != topLevelSheets.end();
1883 bool rootIsVirtualRoot = root == &sch->Root() || root->IsVirtualRootSheet();
1884 bool changed = false;
1885
1886 if( !rootIsTopLevel && !rootIsVirtualRoot )
1887 {
1888 topLevelSheets = { root };
1889 changed = true;
1890 }
1891
1892 // A flat hierarchy records its other top-level sheets only in the project file. They
1893 // must load too, or classification never sees them and their trees never convert.
1894 if( !rootIsVirtualRoot )
1895 {
1896 for( const TOP_LEVEL_SHEET_INFO& info : projectFile.GetTopLevelSheets() )
1897 {
1898 wxFileName fn( projectPtr->GetProjectPath(), info.filename );
1899 fn.MakeAbsolute();
1900
1901 if( fn.GetFullPath() == job->m_filename )
1902 continue;
1903
1904 SCH_SHEET* sibling = pi->LoadSchematicFile( fn.GetFullPath(), sch );
1905
1906 if( !sibling || !pi->GetError().IsEmpty() )
1907 {
1908 m_reporter->Report( wxString::Format( _( "Cannot export: the schematic did not load "
1909 "completely.\n%s" ),
1910 pi->GetError() ),
1913 }
1914
1915 if( info.uuid != niluuid )
1916 const_cast<KIID&>( sibling->m_Uuid ) = info.uuid;
1917
1918 sibling->SetName( info.name );
1919 topLevelSheets.push_back( sibling );
1920 changed = true;
1921 }
1922 }
1923
1924 if( changed )
1925 sch->SetTopLevelSheets( topLevelSheets );
1926
1927 sch->LoadVariants();
1928 }
1929 catch( const IO_ERROR& ioe )
1930 {
1931 m_reporter->Report( wxString::Format( _( "Error loading schematic: %s\n" ), ioe.What() ), RPT_SEVERITY_ERROR );
1933 }
1934
1935 // A screen can be shared by several sheets, so gather each one once.
1936 std::set<SCH_SCREEN*> seen;
1937 std::vector<std::pair<SCH_SHEET*, SCH_SCREEN*>> screens;
1938
1939 for( const SCH_SHEET_PATH& path : sch->BuildUnorderedSheetList() )
1940 {
1941 SCH_SHEET* sheet = path.Last();
1942 SCH_SCREEN* screen = sheet ? sheet->GetScreen() : nullptr;
1943
1944 if( screen && seen.insert( screen ).second )
1945 screens.emplace_back( sheet, screen );
1946 }
1947
1948 COMPATIBILITY_REPORT report;
1949
1950 // Variants (20250922) are schematic-wide, so report them once for the whole schematic.
1951 int variantCount = static_cast<int>( sch->GetVariantNames().size() );
1952
1953 if( target->m_schVersion < SCH_VER_VARIANTS && variantCount > 0 )
1954 {
1955 job->m_foundVariants.assign( sch->GetVariantNames().begin(), sch->GetVariantNames().end() );
1956 }
1957
1958 if( !job->m_variant.IsEmpty() )
1959 {
1960 if( target->m_schVersion >= SCH_VER_VARIANTS )
1961 {
1962 m_reporter->Report( _( "The target supports variants, so there is nothing to flatten.\n" ),
1965 }
1966
1967 if( !sch->GetVariantNames().count( job->m_variant ) )
1968 {
1969 m_reporter->Report( wxString::Format( _( "The schematic has no variant named '%s'.\n" ), job->m_variant ),
1972 }
1973
1974 wxString conflict;
1975
1976 if( !FlattenSchematicVariant( *sch, job->m_variant, &conflict ) )
1977 {
1978 m_reporter->Report(
1979 wxString::Format( _( "Cannot export selected variant '%s': %s" ), job->m_variant, conflict )
1980 + wxS( "\n" ),
1983 }
1984 }
1985
1986 for( const auto& [sheet, screen] : screens )
1987 {
1988 report.Merge( ClassifyScreenForDowngrade( screen, *target, job->m_dropInsteadOfApproximate ) );
1989 }
1990
1991 if( target->m_schVersion < SCH_VER_VARIANTS && variantCount > 0 )
1992 {
1993 report.Add( DOWNGRADE_BUCKET::DROP, _( "Schematic variants" ), _( "The variant registry is removed." ),
1994 variantCount );
1995 }
1996
1997 report.Merge( ClassifySchematicStructureForDowngrade( *sch, *target ) );
1998
1999 // Net chains are connectivity-derived and a plain reload drops them. Detect them in the source
2000 // so the user is warned.
2001 if( target->m_schVersion < SCH_VER_NET_CHAINS )
2002 {
2003 wxString rootContent;
2004 wxFFile rootFile( job->m_filename, wxT( "rb" ) );
2005
2006 if( rootFile.IsOpened() && rootFile.ReadAll( &rootContent ) && rootContent.Contains( wxT( "(net_chain" ) ) )
2007 {
2008 report.Add( DOWNGRADE_BUCKET::DROP, _( "Net chains" ),
2009 _( "No equivalent in the target. Net chain grouping is removed." ), 1 );
2010 }
2011 }
2012
2013 // Library losses belong in the report the user approves too.
2014 if( !job->m_libraryDir.IsEmpty() )
2016
2017 job->m_report = report;
2018
2019 report.Print( m_reporter );
2020
2021 if( job->m_dryRun )
2023
2024 if( report.IsBlocked() )
2025 {
2026 m_reporter->Report( _( "Cannot export: the schematic uses features the target cannot represent.\n" ),
2029 }
2030
2031 if( report.IsLossy() && !job->m_force )
2032 {
2033 m_reporter->Report( _( "Export would lose data. Re-run with --force to export anyway.\n" ),
2036 }
2037
2038 if( !libraryCopyDir.IsEmpty() )
2039 {
2040 wxFileName base( job->m_filename );
2041 base.MakeAbsolute();
2042 const wxString rootPrefix = wxFileName::DirName( base.GetPath() ).GetPathWithSep();
2043 std::vector<wxString> destinations;
2044
2045 for( const auto& [sheet, screen] : screens )
2046 {
2047 wxFileName source( screen->GetFileName() );
2048
2049 if( !source.IsAbsolute() )
2050 source.MakeAbsolute( base.GetPath() );
2051
2052 source.Normalize( wxPATH_NORM_DOTS );
2053 const wxString sourcePath = source.GetFullPath();
2054 const bool contained = wxFileName::IsCaseSensitive() ? sourcePath.StartsWith( rootPrefix )
2055 : sourcePath.Lower().StartsWith( rootPrefix.Lower() );
2056
2057 if( !contained )
2058 {
2059 m_reporter->Report(
2060 wxString::Format( _( "Cannot export: sheet '%s' lies outside the project folder.\n" ),
2061 sourcePath ),
2064 }
2065
2066 destinations.push_back( job->m_outputDir + wxFileName::GetPathSeparator()
2067 + sourcePath.Mid( rootPrefix.length() ) );
2068 }
2069
2070 wxFileName projectOutput = projectFn;
2071 projectOutput.SetPath( job->m_outputDir );
2072 destinations.push_back( projectOutput.GetFullPath() );
2073 std::error_code error;
2074 wxString library =
2075 DowngradeWxPath( std::filesystem::weakly_canonical( DowngradeNativePath( libraryCopyDir ), error ) );
2076
2077 if( error )
2078 {
2079 m_reporter->Report( _( "The library output path is not accessible.\n" ), RPT_SEVERITY_ERROR );
2081 }
2082
2083 for( const wxString& destination : destinations )
2084 {
2085 wxString path =
2086 DowngradeWxPath( std::filesystem::weakly_canonical( DowngradeNativePath( destination ), error ) );
2087
2088 if( error )
2089 {
2090 m_reporter->Report( _( "The schematic output path is not accessible.\n" ), RPT_SEVERITY_ERROR );
2092 }
2093
2094 wxString libraryPath = library;
2095
2096 if( !wxFileName::IsCaseSensitive() )
2097 {
2098 path.MakeLower();
2099 libraryPath.MakeLower();
2100 }
2101
2102 const wxString separator( wxFileName::GetPathSeparator() );
2103
2104 if( path == libraryPath || path.StartsWith( libraryPath + separator )
2105 || libraryPath.StartsWith( path + separator ) )
2106 {
2107 m_reporter->Report( _( "The copied library folder overlaps a schematic or project output path. "
2108 "Choose a different library folder.\n" ),
2111 }
2112 }
2113 }
2114
2115 if( !prepareOutput() )
2117
2118 if( target->m_schVersion < SCH_VER_VARIANTS )
2119 {
2120 std::vector<wxString> variants( sch->GetVariantNames().begin(), sch->GetVariantNames().end() );
2121
2122 for( const wxString& name : variants )
2123 sch->DeleteVariant( name );
2124 }
2125
2126 // Keep designs, companion metadata, and libraries staged until every file is verified.
2127 DOWNGRADE_FILE_TRANSACTION transaction;
2128 wxString currentTemp;
2129
2130 try
2131 {
2132 wxFileName base( job->m_filename );
2133 base.MakeAbsolute();
2134
2135 for( const auto& [sheet, screen] : screens )
2136 {
2137 DowngradeScreenInPlace( screen, *target, job->m_dropInsteadOfApproximate );
2138
2139 wxFileName screenFn( screen->GetFileName() );
2140
2141 if( !screenFn.IsAbsolute() )
2142 screenFn.MakeAbsolute( base.GetPath() );
2143
2144 // Resolve embedded dot segments, or an absolute path with ../ could sidestep the check.
2145 screenFn.Normalize( wxPATH_NORM_DOTS );
2146
2147 // Never write outside the tree that holds the root schematic. A sheet with an absolute
2148 // or ../ path would otherwise overwrite the user's original shared sheet.
2149 wxFileName rootDir( base.GetPath(), wxEmptyString );
2150
2151 wxString screenPath = screenFn.GetFullPath();
2152 wxString rootPrefix = rootDir.GetPathWithSep();
2153
2154 // Case-insensitive filesystems must not let a case mismatch sidestep the check.
2155 bool contained = wxFileName::IsCaseSensitive() ? screenPath.StartsWith( rootPrefix )
2156 : screenPath.Lower().StartsWith( rootPrefix.Lower() );
2157
2158 if( !contained )
2159 {
2160 m_reporter->Report( wxString::Format( _( "Cannot export: sheet '%s' lies outside the project "
2161 "folder.\n" ),
2162 screenFn.GetFullPath() ),
2165 }
2166
2167 wxString finalPath = screenFn.GetFullPath();
2168
2169 // With an output folder, mirror the sheet's place in the tree there instead of
2170 // rewriting the original.
2171 if( !job->m_outputDir.IsEmpty() )
2172 {
2173 wxFileName destFn( job->m_outputDir + wxFileName::GetPathSeparator()
2174 + screenPath.Mid( rootPrefix.length() ) );
2175
2176 if( !wxDirExists( destFn.GetPath() )
2177 && !wxFileName::Mkdir( destFn.GetPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
2178 {
2179 m_reporter->Report(
2180 wxString::Format( _( "Could not create the output folder '%s'.\n" ), destFn.GetPath() ),
2183 }
2184
2185 finalPath = destFn.GetFullPath();
2186 }
2187
2188 if( !job->m_inPlace && !ValidateFreshDowngradeOutput( finalPath, outputError ) )
2189 THROW_IO_ERROR( outputError );
2190
2191 currentTemp = transaction.Stage( finalPath );
2192
2193 if( currentTemp.IsEmpty() )
2194 THROW_IO_ERROR( _( "Could not stage the schematic export." ) );
2195
2196 SaveSchematicForTarget( sheet, sch, currentTemp, *target );
2197
2198 wxString unsupported;
2199
2200 bool good = VerifyDowngradedFile(
2201 currentTemp, target->m_schVersion,
2202 [&]( const wxString& aText )
2203 {
2204 return FindUnsupportedSchToken( aText, *target );
2205 },
2206 &unsupported );
2207
2208 if( !good )
2209 {
2210 if( !unsupported.IsEmpty() )
2211 m_reporter->Report( wxString::Format( _( "Cannot export: the result still uses '%s', which %s "
2212 "cannot open. This feature has no downgrade rule "
2213 "yet.\n" ),
2214 unsupported, target->m_name ),
2216 else
2217 m_reporter->Report(
2218 wxString::Format( _( "Cannot export: could not verify '%s'.\n" ), screenFn.GetFullPath() ),
2220
2222 }
2223
2224 currentTemp.Clear();
2225 }
2226
2227 if( target->m_schVersion >= SCH_VER_VARIANTS )
2228 {
2229 nlohmann::json document = nlohmann::json::object();
2230 wxString content;
2231
2232 if( projectFn.FileExists() )
2233 {
2234 wxFFile sourceProject( projectFn.GetFullPath(), wxT( "rb" ) );
2235
2236 if( !sourceProject.IsOpened() || !sourceProject.ReadAll( &content ) )
2237 THROW_IO_ERROR( _( "Could not read the schematic project settings." ) );
2238
2239 try
2240 {
2241 document = nlohmann::json::parse( content.ToStdString( wxConvUTF8 ) );
2242 }
2243 catch( const nlohmann::json::exception& )
2244 {
2245 THROW_IO_ERROR( _( "Could not read the schematic project settings." ) );
2246 }
2247 }
2248
2249 if( !document.is_object() )
2250 THROW_IO_ERROR( _( "The schematic project settings are invalid." ) );
2251
2252 nlohmann::json aliases = nlohmann::json::object();
2253
2254 for( const auto& alias : sch->GetAllBusAliases() )
2255 {
2256 nlohmann::json members = nlohmann::json::array();
2257
2258 for( const wxString& member : alias->Members() )
2259 members.push_back( member.ToStdString( wxConvUTF8 ) );
2260
2261 aliases[alias->GetName().ToStdString( wxConvUTF8 )] = std::move( members );
2262 }
2263
2264 if( !document.contains( "schematic" ) || !document["schematic"].is_object() )
2265 document["schematic"] = nlohmann::json::object();
2266
2267 nlohmann::json& schematicSettings = document["schematic"];
2268 bool needsProject = !job->m_outputDir.IsEmpty()
2269 || schematicSettings.value( "bus_aliases", nlohmann::json::object() ) != aliases;
2270
2271 if( needsProject )
2272 {
2273 schematicSettings["bus_aliases"] = std::move( aliases );
2274 nlohmann::json roots = nlohmann::json::array();
2275
2276 for( const TOP_LEVEL_SHEET_INFO& root : projectFile.GetTopLevelSheets() )
2277 {
2278 wxFileName filename( root.filename );
2279 filename.MakeAbsolute( projectPtr->GetProjectPath() );
2280 filename.MakeRelativeTo( base.GetPath() );
2281 roots.push_back( { { "uuid", root.uuid.AsString().ToStdString( wxConvUTF8 ) },
2282 { "name", root.name.ToStdString( wxConvUTF8 ) },
2283 { "filename", filename.GetFullPath().ToStdString( wxConvUTF8 ) } } );
2284 }
2285
2286 schematicSettings["top_level_sheets"] = roots;
2287
2288 if( !document.contains( "meta" ) )
2289 document["meta"] = { { "version", 3 } };
2290
2291 wxFileName destination = projectFn;
2292
2293 if( !job->m_outputDir.IsEmpty() )
2294 destination.SetPath( job->m_outputDir );
2295
2296 wxString finalPath = destination.GetFullPath();
2297 if( !job->m_inPlace && !ValidateFreshDowngradeOutput( finalPath, outputError ) )
2298 THROW_IO_ERROR( outputError );
2299
2300 currentTemp = transaction.Stage( finalPath );
2301
2302 if( currentTemp.IsEmpty() )
2303 THROW_IO_ERROR( _( "Could not stage the schematic project settings." ) );
2304
2305 wxFFile outputProject( currentTemp, wxT( "wb" ) );
2306
2307 if( !outputProject.IsOpened()
2308 || !outputProject.Write( wxString::FromUTF8( ( document.dump( 2 ) + "\n" ).c_str() ) )
2309 || !outputProject.Close() )
2310 {
2311 THROW_IO_ERROR( _( "Could not save the schematic project settings." ) );
2312 }
2313
2314 currentTemp.Clear();
2315 }
2316 }
2317 }
2318 catch( const IO_ERROR& ioe )
2319 {
2320 m_reporter->Report(
2321 wxString::Format( _( "Error saving schematic file '%s'.\n%s" ), job->m_filename, ioe.What().GetData() ),
2324 }
2325
2326 if( int libResult = downgradeLibraries( &transaction ); libResult != CLI::EXIT_CODES::SUCCESS )
2327 return libResult;
2328
2329 wxString failedDestination = transaction.Commit();
2330
2331 if( !failedDestination.IsEmpty() )
2332 {
2333 m_reporter->Report( wxString::Format( _( "Could not replace '%s'.\n" ), failedDestination ),
2335
2336 if( !transaction.GetRecoveryError().IsEmpty() )
2337 m_reporter->Report( transaction.GetRecoveryError(), RPT_SEVERITY_ERROR );
2338
2340 }
2341
2342 outputDirectories.Commit();
2343 m_reporter->Report( _( "Saved downgraded schematic\n" ), RPT_SEVERITY_INFO );
2344
2346}
2347
2348
2350{
2351 JOB_SCH_IMPORT* job = dynamic_cast<JOB_SCH_IMPORT*>( aJob );
2352
2353 if( !job )
2355
2356 job->m_netNameMap.clear();
2357
2358 if( !wxFile::Exists( job->m_inputFile ) )
2359 {
2360 m_reporter->Report( wxString::Format( _( "Input file not found: '%s'\n" ), job->m_inputFile ),
2363 }
2364
2365 // AUTO restricts autodetect to non-KiCad plugins so a native file is not re-imported.
2366 SCH_IO_MGR::SCH_FILE_T fileType = SCH_IO_MGR::SCH_FILE_UNKNOWN;
2367
2368 switch( job->m_format )
2369 {
2372 break;
2373 case JOB_SCH_IMPORT::FORMAT::ALTIUM: fileType = SCH_IO_MGR::SCH_ALTIUM; break;
2374 case JOB_SCH_IMPORT::FORMAT::EAGLE: fileType = SCH_IO_MGR::SCH_EAGLE; break;
2375 case JOB_SCH_IMPORT::FORMAT::CADSTAR: fileType = SCH_IO_MGR::SCH_CADSTAR_ARCHIVE; break;
2376 case JOB_SCH_IMPORT::FORMAT::EASYEDA: fileType = SCH_IO_MGR::SCH_EASYEDA; break;
2377 case JOB_SCH_IMPORT::FORMAT::EASYEDAPRO: fileType = SCH_IO_MGR::SCH_EASYEDAPRO; break;
2378 case JOB_SCH_IMPORT::FORMAT::LTSPICE: fileType = SCH_IO_MGR::SCH_LTSPICE; break;
2379 case JOB_SCH_IMPORT::FORMAT::PADS: fileType = SCH_IO_MGR::SCH_PADS; break;
2380 case JOB_SCH_IMPORT::FORMAT::DIPTRACE: fileType = SCH_IO_MGR::SCH_DIPTRACE; break;
2381 case JOB_SCH_IMPORT::FORMAT::PCAD: fileType = SCH_IO_MGR::SCH_PCAD; break;
2382 case JOB_SCH_IMPORT::FORMAT::ORCAD: fileType = SCH_IO_MGR::SCH_ORCAD; break;
2383 case JOB_SCH_IMPORT::FORMAT::EASYPC: fileType = SCH_IO_MGR::SCH_EASYPC; break;
2384 case JOB_SCH_IMPORT::FORMAT::TARGET3001: fileType = SCH_IO_MGR::SCH_TARGET3001; break;
2385 }
2386
2387 if( fileType == SCH_IO_MGR::SCH_FILE_UNKNOWN )
2388 {
2389 // Quiet sentinel: lets the top-level `import` command treat the file as not-a-schematic.
2390 m_reporter->Report( wxString::Format( _( "No schematic importer recognizes the file format "
2391 "of '%s'\n" ),
2392 job->m_inputFile ),
2395 }
2396
2397 wxString outputPath = job->GetConfiguredOutputPath();
2398
2399 if( outputPath.IsEmpty() )
2401
2402 wxFileName inputFn( job->m_inputFile );
2403 inputFn.MakeAbsolute();
2404
2405 wxFileName outputFn( outputPath );
2406 outputFn.MakeAbsolute();
2407
2408 // Foreign importers resolve their symbol library against the *active* project, so an import
2409 // with no project loaded needs a transient active one at the output location (never written to
2410 // disk; LoadProject returns false yet still registers it, hence the GetProject() check).
2412 PROJECT* projectPtr = nullptr;
2413 bool createdTransientProject = false;
2414
2415 if( mgr.IsProjectOpenNotDummy() )
2416 {
2417 projectPtr = &mgr.Prj();
2418 }
2419 else
2420 {
2421 wxFileName projectFn( outputFn );
2422 projectFn.SetExt( FILEEXT::ProjectFileExtension );
2423
2424 mgr.LoadProject( projectFn.GetFullPath(), true );
2425 projectPtr = mgr.GetProject( projectFn.GetFullPath() );
2426 createdTransientProject = ( projectPtr != nullptr );
2427 }
2428
2429 if( !projectPtr )
2430 {
2431 m_reporter->Report( _( "Could not establish a project for the import\n" ),
2434 }
2435
2436 PROJECT& project = *projectPtr;
2437
2438 // Declared before the SCHEMATIC so reverse-destruction tears the schematic (which references
2439 // the project) down first; unloads the transient project on every exit path.
2440 TRANSIENT_PROJECT_GUARD transientProjectGuard{ mgr, projectPtr, createdTransientProject };
2441
2443
2444 std::unique_ptr<SCHEMATIC> schematic = std::make_unique<SCHEMATIC>( &project );
2445
2446 wxString formatName = SCH_IO_MGR::ShowType( fileType );
2447 SCH_SHEET* loadedSheet = nullptr;
2448
2449 // Declared before the plugin so it outlives the plugin's pointer to it
2450 IMPORT_MESSAGE_COLLECTOR importMessages( *m_reporter );
2451
2452 // outlives the load so the retained symbol definitions can be reconciled below
2453 IO_RELEASER<SCH_IO> pi( SCH_IO_MGR::FindPlugin( fileType ) );
2454
2455 if( !pi )
2456 {
2457 m_reporter->Report( wxString::Format( _( "No plugin found for file type '%s'\n" ),
2458 formatName ),
2461 }
2462
2463 // Without this the importer's warnings go to the wx log and never reach the import report
2464 pi->SetReporter( &importMessages );
2465
2466 try
2467 {
2468 m_reporter->Report( wxString::Format( _( "Importing '%s' using %s format...\n" ),
2469 inputFn.GetFullPath(), formatName ),
2471
2472 loadedSheet = pi->LoadSchematicFile( inputFn.GetFullPath(), schematic.get() );
2473
2474 if( !loadedSheet )
2475 {
2476 m_reporter->Report( _( "Failed to load schematic\n" ), RPT_SEVERITY_ERROR );
2478 }
2479 }
2480 catch( const IO_ERROR& ioe )
2481 {
2482 m_reporter->Report( wxString::Format( _( "Error during import: %s\n" ), ioe.What() ),
2485 }
2486
2487 size_t symbolCount = 0;
2488 size_t sheetCount = 0;
2489
2490 try
2491 {
2492 // Some importers build the top-level sheet set themselves; only collapse to the returned
2493 // sheet otherwise (mirrors SCH_EDIT_FRAME::importFile()).
2494 std::vector<SCH_SHEET*> topLevelSheets = schematic->GetTopLevelSheets();
2495 bool loadedIsTopLevel = std::find( topLevelSheets.begin(), topLevelSheets.end(),
2496 loadedSheet ) != topLevelSheets.end();
2497 bool loadedIsVirtualRoot = loadedSheet == &schematic->Root()
2498 || loadedSheet->IsVirtualRootSheet();
2499
2500 if( !loadedIsTopLevel && !loadedIsVirtualRoot )
2501 schematic->SetTopLevelSheets( { loadedSheet } );
2502
2503 // Extract a project symbol library and re-link LIB_IDs, as importFile() does; without it
2504 // the saved schematic references nicknames no library table row resolves.
2505 ReconcileImportedSymbols( *pi, *schematic, project, inputFn.GetFullPath(), nullptr,
2506 *m_reporter );
2507
2508 // Recompute connectivity so instance data is valid before saving, as importFile() does.
2509 std::unique_ptr<TOOL_MANAGER> toolManager = std::make_unique<TOOL_MANAGER>();
2510 toolManager->SetEnvironment( schematic.get(), nullptr, nullptr, Kiface().KifaceSettings(),
2511 nullptr );
2512
2513 {
2514 SCH_COMMIT dummyCommit( toolManager.get() );
2515 schematic->RecalculateConnections( &dummyCommit, GLOBAL_CLEANUP, toolManager.get() );
2516 dummyCommit.Push( _( "Schematic Cleanup" ), SKIP_UNDO | SKIP_CONNECTIVITY | DELETE_REMOVED_ITEMS );
2517 }
2518
2519 schematic->SetSheetNumberAndCount();
2520
2521 // Imported schematics preserve their source page frame as schematic graphics.
2522 // Match the editor import path and suppress KiCad's default frame/title block.
2523 schematic->Settings().m_SchDrawingSheetFileName = wxS( "empty.kicad_wks" );
2525
2526 if( SCH_SHEET* topSheet = schematic->GetTopLevelSheet() )
2527 topSheet->SetFileName( outputFn.GetFullName() );
2528
2529 schematic->RootScreen()->SetFileName( outputFn.GetFullPath() );
2530
2531 SCH_SCREENS screens( schematic->Root() );
2532
2533 std::unordered_map<SCH_SCREEN*, wxString> filenameMap;
2534 filenameMap[schematic->RootScreen()] = outputFn.GetFullPath();
2535
2536 wxString errorMsg;
2537
2538 if( !PrepareSaveAsFiles( *schematic, screens, inputFn, outputFn, /*aSaveCopy*/ true,
2539 /*aCopySubsheets*/ true, /*aIncludeExternSheets*/ true,
2540 filenameMap, errorMsg ) )
2541 {
2542 m_reporter->Report( errorMsg + wxS( "\n" ), RPT_SEVERITY_ERROR );
2544 }
2545
2546 // PrepareSaveAsFiles seeds an entry (empty for sheets it does not relocate) for every
2547 // screen; empty paths are skipped.
2548 IO_RELEASER<SCH_IO> kicadPi( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
2549
2550 for( size_t i = 0; i < screens.GetCount(); i++ )
2551 {
2552 SCH_SCREEN* screen = screens.GetScreen( i );
2553 wxString path = filenameMap[screen];
2554
2555 if( path.IsEmpty() )
2556 continue;
2557
2558 wxFileName fn( path );
2560
2561 kicadPi->SaveSchematicFile( fn.GetFullPath(), screens.GetSheet( i ), schematic.get() );
2562 sheetCount++;
2563
2564 auto symbols = screen->Items().OfType( SCH_SYMBOL_T );
2565 symbolCount += std::distance( symbols.begin(), symbols.end() );
2566 }
2567 }
2568 catch( const IO_ERROR& ioe )
2569 {
2570 m_reporter->Report( wxString::Format( _( "Error saving imported schematic: %s\n" ),
2571 ioe.What() ),
2574 }
2575 catch( const std::exception& exc )
2576 {
2577 m_reporter->Report( wxString::Format( _( "Error saving imported schematic: %s\n" ),
2578 exc.what() ),
2581 }
2582
2583 // The board job renames its nets from this; nothing is written beside the schematic.
2584 if( const IMPORT_NET_MAP* map = schematic->GetImportNetMap() )
2586
2587 m_reporter->Report( wxString::Format( _( "Successfully saved imported schematic to '%s'\n" ),
2588 outputFn.GetFullPath() ),
2590
2591 // Linked by the top-level `import` command's subsequent SaveProject().
2592 if( Pgm().GetSettingsManager().IsProjectOpenNotDummy() )
2593 {
2594 std::vector<FILE_INFO_PAIR>& projectSheets = project.GetProjectFile().GetSheets();
2595 projectSheets.clear();
2596
2597 for( const SCH_SHEET_PATH& sheetPath : schematic->Hierarchy() )
2598 {
2599 SCH_SHEET* sheet = sheetPath.Last();
2600
2601 if( sheet && !sheet->IsVirtualRootSheet() )
2602 projectSheets.emplace_back( std::make_pair( sheet->m_Uuid, sheet->GetName() ) );
2603 }
2604
2605 // Reload uses the top-level sheet list, not the UUID-to-name map.
2606 const std::vector<SCH_SHEET*>& topLevelSheets = schematic->GetTopLevelSheets();
2607
2608 if( !topLevelSheets.empty() )
2609 {
2610 std::vector<TOP_LEVEL_SHEET_INFO>& infos = project.GetProjectFile().GetTopLevelSheets();
2611 infos.clear();
2612
2613 wxString projectPath = project.GetProjectPath();
2614
2615 for( SCH_SHEET* sheet : topLevelSheets )
2616 {
2617 if( !sheet || !sheet->GetScreen() )
2618 continue;
2619
2620 wxFileName sheetFn( sheet->GetScreen()->GetFileName() );
2621
2622 if( sheetFn.IsAbsolute() )
2623 sheetFn.MakeRelativeTo( projectPath );
2624
2625 infos.emplace_back( sheet->m_Uuid, sheet->GetName(), sheetFn.GetFullPath() );
2626 }
2627 }
2628 }
2629
2630 // Extra top-level sheets need a project file to remain reachable.
2631 if( createdTransientProject && schematic->GetTopLevelSheets().size() > 1 )
2632 {
2633 m_reporter->Report( wxString::Format( _( "%zu sheets were written, but only '%s' is "
2634 "reachable: top-level sheets are recorded in a "
2635 "project file. Use 'kicad-cli import' to create "
2636 "one.\n" ),
2637 sheetCount, outputFn.GetFullName() ),
2639 }
2640
2641 IMPORT_REPORT_DATA reportData;
2642 importMessages.Flush( job->m_reportFormat, reportData );
2643
2645 {
2646 reportData.m_sourceFile = inputFn.GetFullName();
2647 reportData.m_sourceFormat = formatName;
2648 reportData.m_outputFile = outputFn.GetFullName();
2649 reportData.m_statistics = {
2650 { wxS( "symbols" ), symbolCount },
2651 { wxS( "sheets" ), sheetCount }
2652 };
2653
2654 reportData.m_statistics.emplace_back( wxS( "renamed_board_nets" ), job->m_netNameMap.size() );
2655
2656 if( !WriteImportReport( m_reporter, job->m_reportFormat, job->m_reportFile, reportData ) )
2658 }
2659
2661}
2662
2663
2665{
2666 DS_PROXY_VIEW_ITEM* drawingSheet =
2668 &aSch->RootScreen()->GetTitleBlock(), aSch->GetProperties() );
2669
2670 drawingSheet->SetPageNumber( TO_UTF8( aSch->RootScreen()->GetPageNumber() ) );
2671 drawingSheet->SetSheetCount( aSch->RootScreen()->GetPageCount() );
2672 drawingSheet->SetFileName( TO_UTF8( aSch->RootScreen()->GetFileName() ) );
2675 drawingSheet->SetIsFirstPage( aSch->RootScreen()->GetVirtualPageNumber() == 1 );
2676
2677 wxString currentVariant = aSch->GetCurrentVariant();
2678 wxString variantDesc = aSch->GetVariantDescription( currentVariant );
2679 drawingSheet->SetVariantName( TO_UTF8( currentVariant ) );
2680 drawingSheet->SetVariantDesc( TO_UTF8( variantDesc ) );
2681
2682 drawingSheet->SetSheetName( "" );
2683 drawingSheet->SetSheetPath( "" );
2684
2685 return drawingSheet;
2686}
2687
2688
2689// ============================================================================
2690// JobSchDiff: sch_diff implementation
2691// ============================================================================
2694#include <diff_merge/diff_scene.h>
2695#include <diff_merge/sch_differ.h>
2699#include <project/project_file.h>
2701#include <jobs/job_sch_diff.h>
2702#include <jobs/scratch_doc.h>
2703#include <wx/file.h>
2704
2705
2706// Load a schematic into a SCRATCH_DOC<SCHEMATIC> that keeps a dedicated scratch
2707// PROJECT attached for the document's lifetime. Without a per-document project,
2708// a second LoadProject(path, true) destroys the first project and the first
2709// schematic's m_project dangles. The destructor severs the link via
2710// SetProject( nullptr ). Shared by every SCH diff/merge job.
2712{
2714 aMgr, aPath,
2715 [aPath]( PROJECT* aProject )
2716 {
2717 return std::unique_ptr<SCHEMATIC>(
2719 /*aSetActive=*/false,
2720 /*aForceDefaultProject=*/false, aProject,
2721 /*aCalculateConnectivity=*/false ) );
2722 },
2723 []( SCHEMATIC* aSch )
2724 {
2725 aSch->SetProject( nullptr );
2726 } );
2727}
2728
2729
2730// A bare PROJECT has no PROJECT_FILE and SCHEMATIC::SetProject dereferences GetProjectFile(),
2731// so the missing side of a diff needs a real scratch project just like the loaded side.
2733{
2734 // A fixed name in the shared temp directory could be loaded as the scratch project, and
2735 // would collide between two KiCad instances diffing at once.
2736 wxFileName synthetic( wxFileName::GetTempDir(),
2737 wxString::Format( wxS( "%s_%ld_%p" ), aTag, static_cast<long>( wxGetProcessId() ), &aMgr ) );
2738
2740 aMgr, synthetic.GetFullPath(),
2741 []( PROJECT* aProject )
2742 {
2743 auto sch = std::make_unique<SCHEMATIC>( aProject );
2744 sch->CreateDefaultScreens();
2745 return sch;
2746 },
2747 []( SCHEMATIC* aSch )
2748 {
2749 aSch->SetProject( nullptr );
2750 } );
2751}
2752
2753
2755{
2756 JOB_SCH_DIFF* diffJob = dynamic_cast<JOB_SCH_DIFF*>( aJob );
2757
2758 if( !diffJob )
2760
2761 // Two schematics in the same SettingsManager need scratch PROJECTs;
2762 // otherwise the second LoadProject(path, true) destroys the first
2763 // project and the first schematic's m_project dangles, crashing on
2764 // any per-instance bbox / field / reference resolution (e.g. inside
2765 // SCH_DIFFER's makeDescriptor calling SCH_SYMBOL::GetRef).
2767
2770
2771 if( !a.doc )
2772 {
2773 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), diffJob->m_inputA ), RPT_SEVERITY_ERROR );
2775 }
2776
2777 if( !b.doc )
2778 {
2779 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), diffJob->m_inputB ), RPT_SEVERITY_ERROR );
2781 }
2782
2783 SCHEMATIC* schA = a.doc.get();
2784 SCHEMATIC* schB = b.doc.get();
2785
2786 KICAD_DIFF::SCH_DIFFER differ( schA, schB, diffJob->m_inputB );
2788
2789 int diffExitCode = KICAD_DIFF::DiffExitCode( result );
2790
2791 if( diffJob->m_exitCodeOnly )
2792 return diffExitCode;
2793
2794 // The schematic geometry (wires, junctions, symbol/sheet/label bbox
2795 // outlines) renders beneath the change rectangles for PNG/SVG, matching
2796 // the interactive dialog.
2798 KICAD_DIFF::MakeEmitOptions( *diffJob, diffJob->m_inputA, diffJob->m_inputB );
2800 emitOpts.referenceGeometry = [&]( const KIGFX::COLOR4D& aColor )
2801 { return KICAD_DIFF::ExtractSchematicGeometry( *schA, aColor ); };
2802 emitOpts.comparisonGeometry = [&]( const KIGFX::COLOR4D& aColor )
2803 { return KICAD_DIFF::ExtractSchematicGeometry( *schB, aColor ); };
2804
2805 return KICAD_DIFF::EmitDiffResult( result, emitOpts, diffExitCode, *m_reporter );
2806}
2807
2808
2809// ============================================================================
2810// JobSymDiff: sym_diff implementation
2811// ============================================================================
2813#include <jobs/job_sym_diff.h>
2814
2815
2816// Load one side of a symbol-library diff into its owner vector and name map.
2817// When aAllowEmpty is set an empty path resolves to a clean (empty) side; the
2818// non-interactive job path leaves it unset so a missing path is an input error.
2819static int loadSymbolLibrarySide( const wxString& aPath,
2820 std::vector<std::unique_ptr<LIB_SYMBOL>>& aOwners,
2821 KICAD_DIFF::SYM_LIB_DIFFER::SYMBOL_MAP& aMap, bool aAllowEmpty,
2822 REPORTER& aReporter )
2823{
2824 if( aAllowEmpty && aPath.IsEmpty() )
2826
2827 try
2828 {
2829 auto loaded = KICAD_DIFF::SYM_LIB_DIFFER::LoadLibrary( aPath );
2830 aOwners = std::move( loaded.first );
2831 aMap = std::move( loaded.second );
2833 }
2834 catch( const IO_ERROR& ioe )
2835 {
2836 aReporter.Report( wxString::Format( _( "Failed to load %s: %s\n" ), aPath, ioe.What() ),
2838 }
2839 catch( const std::exception& e )
2840 {
2841 aReporter.Report(
2842 wxString::Format( _( "Failed to load %s: %s\n" ), aPath, wxString::FromUTF8( e.what() ) ),
2844 }
2845
2847}
2848
2849
2850// Flatten a symbol-library name map into a single DOCUMENT_GEOMETRY tinted with
2851// the supplied per-side theme colour.
2854{
2856
2857 for( const auto& [name, symbol] : aMap )
2858 {
2859 if( symbol )
2860 KICAD_DIFF::AppendGeometry( geometry, KICAD_DIFF::ExtractSymbolGeometry( *symbol, aColor ) );
2861 }
2862
2863 return geometry;
2864}
2865
2866
2868{
2869 JOB_SYM_DIFF* diffJob = dynamic_cast<JOB_SYM_DIFF*>( aJob );
2870
2871 if( !diffJob )
2873
2874 std::vector<std::unique_ptr<LIB_SYMBOL>> ownersA;
2875 std::vector<std::unique_ptr<LIB_SYMBOL>> ownersB;
2878
2879 if( int rc = loadSymbolLibrarySide( diffJob->m_inputA, ownersA, mapA, false, *m_reporter );
2881 {
2882 return rc;
2883 }
2884
2885 if( int rc = loadSymbolLibrarySide( diffJob->m_inputB, ownersB, mapB, false, *m_reporter );
2887 {
2888 return rc;
2889 }
2890
2891 KICAD_DIFF::SYM_LIB_DIFFER differ( mapA, mapB, diffJob->m_inputB );
2893
2894 int diffExitCode = KICAD_DIFF::DiffExitCode( result );
2895
2896 if( diffJob->m_exitCodeOnly )
2897 return diffExitCode;
2898
2900 KICAD_DIFF::MakeEmitOptions( *diffJob, diffJob->m_inputA, diffJob->m_inputB );
2902 emitOpts.referenceGeometry = [&]( const KIGFX::COLOR4D& aColor )
2903 { return symbolLibraryGeometry( mapA, aColor ); };
2904 emitOpts.comparisonGeometry = [&]( const KIGFX::COLOR4D& aColor )
2905 { return symbolLibraryGeometry( mapB, aColor ); };
2906
2907 return KICAD_DIFF::EmitDiffResult( result, emitOpts, diffExitCode, *m_reporter );
2908}
2909
2910
2911// ============================================================================
2912// JobOpenDiffDialog: load two on-disk files and open DIALOG_KICAD_DIFF.
2913// Dispatched from the project manager / PR-review dialog via KIWAY.
2914// ============================================================================
2919#include <jobs/scratch_doc.h>
2920
2921
2922// Both schematics of a diff, kept while the caller draws them.
2933
2934
2935static int loadSchDiffSides( SETTINGS_MANAGER& aMgr, REPORTER& aReporter, const wxString& aFileA,
2936 const wxString& aFileB, SCH_DIFF_SIDES& aSides )
2937{
2938 aSides.a = loadScratchSchematic( aMgr, aFileA );
2939 aSides.b = loadScratchSchematic( aMgr, aFileB );
2940
2941 if( !aSides.a.doc && !aFileA.IsEmpty() )
2942 {
2943 aReporter.Report( wxString::Format( _( "Failed to load %s\n" ), aFileA ), RPT_SEVERITY_ERROR );
2945 }
2946
2947 if( !aSides.b.doc && !aFileB.IsEmpty() )
2948 {
2949 aReporter.Report( wxString::Format( _( "Failed to load %s\n" ), aFileB ), RPT_SEVERITY_ERROR );
2951 }
2952
2953 // Synthesize empty SCHEMATICs for any missing side so SCH_DIFFER sees a valid empty
2954 // document. Each empty side needs its own defaults-only project: a bare PROJECT has no
2955 // PROJECT_FILE, and SCHEMATIC::SetProject dereferences GetProjectFile().
2956 if( !aSides.a.doc )
2957 aSides.emptyA = emptyScratchSchematic( aMgr, wxT( "kicad_diff_empty_a" ) );
2958
2959 if( !aSides.b.doc )
2960 aSides.emptyB = emptyScratchSchematic( aMgr, wxT( "kicad_diff_empty_b" ) );
2961
2962 aSides.schA = aSides.a.doc ? aSides.a.doc.get() : aSides.emptyA.doc.get();
2963 aSides.schB = aSides.b.doc ? aSides.b.doc.get() : aSides.emptyB.doc.get();
2964
2965 KICAD_DIFF::SCH_DIFFER differ( aSides.schA, aSides.schB, aFileB );
2966 aSides.diff = differ.Diff();
2967
2969}
2970
2971
2972// The two schematics a window outside the kiface shows, taken off their canvases before they go.
2974{
2975public:
2977 m_before( aBefore ),
2978 m_after( aAfter )
2979 {
2980 }
2981
2983 {
2984 // The painter keeps a schematic pointer, so give the canvases one that points at nothing.
2985 const KICAD_DIFF::DIFF_COLOR_THEME theme;
2986
2987 for( WIDGET_DIFF_CANVAS* canvas : { m_before, m_after } )
2988 {
2989 canvas->SetContextItems( {} );
2990 canvas->SetContextPainter(
2991 KICAD_DIFF::MakeSchDiffContextPainter( canvas->GetGAL(), nullptr, theme.reference ) );
2992 }
2993 }
2994
2996
2997private:
3000};
3001
3002
3004EESCHEMA_JOBS_HANDLER::AttachDiffCanvases( KICAD_DIFF::DOC_KIND aKind, const wxString& aFileA, const wxString& aFileB,
3005 WIDGET_DIFF_CANVAS* aBefore, WIDGET_DIFF_CANVAS* aAfter,
3006 KICAD_DIFF::DOCUMENT_DIFF& aDiff, REPORTER* aReporter )
3007{
3008 SCOPED_SET_RESET<REPORTER*> reporterGuard( m_reporter, aReporter ? aReporter : m_reporter );
3009
3010 if( aKind != KICAD_DIFF::DOC_KIND::SCH )
3011 return nullptr;
3012
3013 auto context = std::make_unique<SCH_DIFF_CANVAS_CONTEXT>( aBefore, aAfter );
3014
3015 if( loadSchDiffSides( Pgm().GetSettingsManager(), *m_reporter, aFileA, aFileB, context->m_sides )
3017 {
3018 return nullptr;
3019 }
3020
3021 const KICAD_DIFF::DIFF_COLOR_THEME theme;
3022 SCHEMATIC* schA = context->m_sides.schA;
3023 SCHEMATIC* schB = context->m_sides.schB;
3024
3025 KICAD_DIFF::ConfigureSchDiffCanvasContext( *aBefore, schA, nullptr, theme.reference, {}, {}, {}, schA->RootScreen(),
3026 nullptr );
3027 KICAD_DIFF::ConfigureSchDiffCanvasContext( *aAfter, nullptr, schB, theme.reference, {}, {}, {}, nullptr,
3028 schB->RootScreen() );
3029
3030 aDiff = context->m_sides.diff;
3031 return context.release();
3032}
3033
3034
3036 const wxString& aFileB, const wxString& aLabelA,
3037 const wxString& aLabelB, wxWindow* aParent,
3038 REPORTER* aReporter )
3039{
3040 // Restore m_reporter on scope exit so a caller's transient (often
3041 // stack-local) reporter doesn't outlive this call as a dangling member.
3043 aReporter ? aReporter : m_reporter );
3044
3045 wxWindow* parent = aParent ? aParent : ( wxTheApp ? wxTheApp->GetTopWindow() : nullptr );
3046
3049 KICAD_DIFF::DOCUMENT_GEOMETRY compGeometry;
3050
3051 switch( aKind )
3052 {
3054 {
3055 SCH_DIFF_SIDES sides;
3056
3057 if( int rc = loadSchDiffSides( Pgm().GetSettingsManager(), *m_reporter, aFileA, aFileB, sides );
3059 {
3060 return rc;
3061 }
3062
3063 SCHEMATIC* schA = sides.schA;
3064 SCHEMATIC* schB = sides.schB;
3065 result = std::move( sides.diff );
3066
3067 const KICAD_DIFF::DIFF_COLOR_THEME theme;
3068 refGeometry = KICAD_DIFF::ExtractSchematicGeometry( *schA, theme.reference );
3069 compGeometry = KICAD_DIFF::ExtractSchematicGeometry( *schB, theme.comparison );
3070
3071 const wxString labelA = aLabelA.IsEmpty() ? aFileA : aLabelA;
3072 const wxString labelB = aLabelB.IsEmpty() ? aFileB : aLabelB;
3073
3074 // A deleted item exists only in the reference schematic, so the comparison-side context cannot
3075 // draw it. Keyed by sheet path, or removing one instance of a shared sheet ghosts the others.
3076 std::map<KIID_PATH, std::set<KIID>> removedBySheet;
3077
3078 for( const KICAD_DIFF::ITEM_CHANGE& change : result.changes )
3079 {
3080 if( change.kind == KICAD_DIFF::CHANGE_KIND::REMOVED && !change.id.empty() )
3081 {
3082 KIID_PATH sheetPath = change.id;
3083 sheetPath.pop_back();
3084 removedBySheet[sheetPath].insert( change.id.back() );
3085 }
3086 }
3087
3088 DIALOG_KICAD_DIFF dlg( parent, labelA, labelB, result, std::move( refGeometry ), std::move( compGeometry ),
3089 [schA, schB, removedBySheet, color = theme.reference]( WIDGET_DIFF_CANVAS& aCanvas,
3090 const KIID_PATH& aSheetPath )
3091 {
3092 SCH_SCREEN* compScreen = schB ? schB->RootScreen() : nullptr;
3093
3094 if( !aSheetPath.empty() && schB )
3095 {
3096 if( auto sp = schB->Hierarchy().GetSheetPathByKIIDPath( aSheetPath, true ) )
3097 compScreen = sp->LastScreen();
3098 }
3099
3100 SCH_SCREEN* refScreen = schA ? schA->RootScreen() : nullptr;
3101
3102 if( !aSheetPath.empty() && schA )
3103 {
3104 if( auto sp = schA->Hierarchy().GetSheetPathByKIIDPath( aSheetPath, true ) )
3105 refScreen = sp->LastScreen();
3106 }
3107
3108 std::vector<KIGFX::VIEW_ITEM*> deletedItems;
3109 auto removedHere = removedBySheet.find( aSheetPath );
3110
3111 // The initial display passes an empty path for the root
3112 // sheet, which the change ids name by uuid.
3113 if( removedHere == removedBySheet.end() && aSheetPath.empty() && schA )
3114 {
3115 KIID_PATH rootPath;
3116 rootPath.push_back( schA->Root().m_Uuid );
3117 removedHere = removedBySheet.find( rootPath );
3118 }
3119
3120 if( refScreen && removedHere != removedBySheet.end() )
3121 {
3122 for( SCH_ITEM* item : refScreen->Items() )
3123 {
3124 if( removedHere->second.count( item->m_Uuid ) )
3125 deletedItems.push_back( item );
3126 }
3127 }
3128
3129 // Drawing the reference too would overlay the old and new
3130 // versions of a moved item, so only its deleted items are used.
3131 KICAD_DIFF::ConfigureSchDiffCanvasContext( aCanvas, nullptr, schB, color, {},
3132 deletedItems, {}, nullptr, compScreen );
3133 } );
3134 dlg.ShowModal();
3135
3137 }
3139 {
3140 std::vector<std::unique_ptr<LIB_SYMBOL>> ownersA;
3141 std::vector<std::unique_ptr<LIB_SYMBOL>> ownersB;
3144
3145 if( int rc = loadSymbolLibrarySide( aFileA, ownersA, mapA, true, *m_reporter );
3147 {
3148 return rc;
3149 }
3150
3151 if( int rc = loadSymbolLibrarySide( aFileB, ownersB, mapB, true, *m_reporter );
3153 {
3154 return rc;
3155 }
3156
3157 KICAD_DIFF::SYM_LIB_DIFFER differ( mapA, mapB, aFileB );
3158 result = differ.Diff();
3159
3160 const KICAD_DIFF::DIFF_COLOR_THEME theme;
3161 refGeometry = symbolLibraryGeometry( mapA, theme.reference );
3162 compGeometry = symbolLibraryGeometry( mapB, theme.comparison );
3163 break;
3164 }
3165 default:
3166 m_reporter->Report( _( "Unsupported document kind for this dispatcher.\n" ), RPT_SEVERITY_ERROR );
3168 }
3169
3170 const wxString labelA = aLabelA.IsEmpty() ? aFileA : aLabelA;
3171 const wxString labelB = aLabelB.IsEmpty() ? aFileB : aLabelB;
3172
3173 DIALOG_KICAD_DIFF dlg( parent, labelA, labelB, result, std::move( refGeometry ), std::move( compGeometry ) );
3174 dlg.ShowModal();
3175
3177}
3178
3179
3180// ============================================================================
3181// JobSchMerge: sch_merge implementation
3182// ============================================================================
3186#include <jobs/scratch_doc.h>
3187
3188
3189int EESCHEMA_JOBS_HANDLER::RunMerge( KICAD_DIFF::DOC_KIND aKind, const wxString& aAncestor,
3190 const wxString& aOurs, const wxString& aTheirs,
3191 const wxString& aOutput, bool aInteractive, bool aSingleFile,
3192 REPORTER* aReporter )
3193{
3194 // Restore m_reporter on scope exit so a caller's transient (often
3195 // stack-local) reporter doesn't outlive this call as a dangling member.
3197 aReporter ? aReporter : m_reporter );
3198
3199 if( aKind == KICAD_DIFF::DOC_KIND::SYM_LIB )
3200 return runSymLibMerge( aAncestor, aOurs, aTheirs, aOutput );
3201
3202 return runSchMerge( aAncestor, aOurs, aTheirs, aOutput, aInteractive );
3203}
3204
3205
3206int EESCHEMA_JOBS_HANDLER::runSchMerge( const wxString& aAncestor, const wxString& aOurs,
3207 const wxString& aTheirs, const wxString& aOutput,
3208 bool aInteractive )
3209{
3211
3212 SCRATCH_DOC<SCHEMATIC> ancestor = loadScratchSchematic( mgr, aAncestor );
3213 SCRATCH_DOC<SCHEMATIC> ours = loadScratchSchematic( mgr, aOurs );
3214 SCRATCH_DOC<SCHEMATIC> theirs = loadScratchSchematic( mgr, aTheirs );
3215
3216 if( !ancestor.doc || !ours.doc || !theirs.doc )
3217 {
3218 m_reporter->Report( _( "Failed to load one or more input schematics\n" ), RPT_SEVERITY_ERROR );
3220 }
3221
3222 // Multi-sheet hierarchies are supported: each non-root sub-sheet is
3223 // written alongside the output root using its original basename. Top-level
3224 // sheets stay singular — multiple roots is an editor invariant the diff
3225 // engine never models, so refuse those.
3226 auto hasSingleRoot = []( const SCHEMATIC* aSch )
3227 {
3228 return aSch->GetTopLevelSheets().size() == 1;
3229 };
3230
3231 if( !hasSingleRoot( ancestor.doc.get() ) || !hasSingleRoot( ours.doc.get() ) || !hasSingleRoot( theirs.doc.get() ) )
3232 {
3233 m_reporter->Report( _( "sch merge requires each input to have a single top-level sheet\n" ),
3236 }
3237
3238 auto loadedVersion = []( const SCHEMATIC* aSch )
3239 {
3240 return aSch->RootScreen() ? aSch->RootScreen()->GetFileFormatVersionAtLoad() : 0;
3241 };
3242
3243 const wxString formatNotice =
3244 KICAD_DIFF::OlderFormatNotice( loadedVersion( ancestor.doc.get() ), loadedVersion( ours.doc.get() ),
3245 loadedVersion( theirs.doc.get() ), SEXPR_SCHEMATIC_FILE_VERSION );
3246
3247 if( !formatNotice.IsEmpty() )
3248 m_reporter->Report( formatNotice + wxS( "\n" ), RPT_SEVERITY_WARNING );
3249
3250 KICAD_DIFF::SCH_DIFFER ourDiff( ancestor.doc.get(), ours.doc.get() );
3251 KICAD_DIFF::SCH_DIFFER theirDiff( ancestor.doc.get(), theirs.doc.get() );
3252
3253 KICAD_DIFF::DOCUMENT_DIFF ourDocDiff = ourDiff.Diff();
3254 KICAD_DIFF::DOCUMENT_DIFF theirDocDiff = theirDiff.Diff();
3255
3257 KICAD_DIFF::MERGE_PLAN plan = engine.Plan( ourDocDiff, theirDocDiff );
3258
3259 if( aInteractive && !KICAD_DIFF::BuildConflictList( plan ).empty() )
3260 {
3261 if( !Pgm().IsGUI() )
3262 {
3263 m_reporter->Report( _( "--interactive requires a GUI KiCad process; the console "
3264 "kicad-cli cannot open dialogs.\n" ),
3267 }
3268
3269 const KICAD_DIFF::DIFF_COLOR_THEME theme;
3271 ctx.formatNotice = formatNotice;
3272
3273 if( ancestor.doc )
3275
3276 if( ours.doc )
3278
3279 if( theirs.doc )
3281
3283 KICAD_DIFF::CollectChangeBBoxes( theirDocDiff, ctx.theirsBBoxes );
3284
3285 ctx.describeItem = [&ours, &theirs, &ancestor]( const KIID_PATH& aId ) -> wxString
3286 {
3287 if( aId.empty() )
3288 return wxEmptyString;
3289
3291
3292 for( SCHEMATIC* doc : { ours.doc.get(), theirs.doc.get(), ancestor.doc.get() } )
3293 {
3294 if( !doc )
3295 continue;
3296
3297 if( SCH_ITEM* item = doc->ResolveItem( aId.back(), nullptr, true ) )
3298 return item->GetItemDescription( &units, false );
3299 }
3300
3301 return wxEmptyString;
3302 };
3303
3304 // A conflict's path is its sheet path followed by the item, so dropping the item
3305 // names the sheet it lives on.
3306 auto screenForConflict = []( SCHEMATIC* aDoc, const std::optional<KIID_PATH>& aId ) -> SCH_SCREEN*
3307 {
3308 if( !aId.has_value() || aId->size() < 2 )
3309 return aDoc->RootScreen();
3310
3311 KIID_PATH sheetPath = *aId;
3312 sheetPath.pop_back();
3313
3315
3316 if( std::optional<SCH_SHEET_PATH> path = sheets.GetSheetPathByKIIDPath( sheetPath ) )
3317 {
3318 if( SCH_SCREEN* screen = path->LastScreen() )
3319 return screen;
3320 }
3321
3322 return aDoc->RootScreen();
3323 };
3324
3325 ctx.drawSide = [&ancestor, &ours, &theirs, theme, screenForConflict]( WIDGET_DIFF_CANVAS& aCanvas,
3327 const std::optional<KIID_PATH>& aId )
3328 {
3329 SCHEMATIC* doc = nullptr;
3330 KIGFX::COLOR4D colour = theme.reference;
3331
3332 switch( aSide )
3333 {
3334 case DIALOG_KICAD_MERGE_3WAY::SIDE::OURS: doc = ours.doc.get(); break;
3335
3337 doc = theirs.doc.get();
3338 colour = theme.comparison;
3339 break;
3340
3341 case DIALOG_KICAD_MERGE_3WAY::SIDE::ANCESTOR: doc = ancestor.doc.get(); break;
3342 }
3343
3344 if( doc )
3345 {
3346 KICAD_DIFF::ConfigureSchDiffCanvasContext( aCanvas, doc, nullptr, colour, {}, {}, {},
3347 screenForConflict( doc, aId ) );
3348 }
3349 };
3350
3351 DIALOG_KICAD_MERGE_3WAY dlg( wxTheApp->GetTopWindow(), plan, std::move( ctx ) );
3352
3353 // Cancelling leaves the conflicts unresolved and the files untouched.
3354 if( dlg.ShowModal() != wxID_APPLY )
3356
3357 plan = dlg.GetResolvedPlan();
3358 }
3359
3360 // Snapshot of the plan before the applier moves it; drives the
3361 // unresolved-conflict report below.
3362 const KICAD_DIFF::MERGE_PLAN planSnapshot = plan;
3363
3364 KICAD_DIFF::SCH_MERGE_APPLIER applier( ancestor.doc.get(), ours.doc.get(), theirs.doc.get(), std::move( plan ) );
3365
3366 if( !applier.Apply() )
3367 {
3368 m_reporter->Report( _( "Merge applier failed to produce a schematic\n" ), RPT_SEVERITY_ERROR );
3370 }
3371
3372 // Sheet add/remove/replace resolutions are explicitly skipped by
3373 // SCH_MERGE_APPLIER (see isSheetItem); succeeding here would silently
3374 // drop hierarchy edits.
3375 if( applier.GetReport().sheetActionsSkipped > 0 )
3376 {
3377 m_reporter->Report( _( "Merge contains hierarchical sheet structure changes that sch merge "
3378 "cannot apply\n" ),
3381 }
3382
3383 // Refusal above guarantees a single top-level sheet.
3384 SCH_SHEET* rootSheet = ancestor.doc->GetTopLevelSheet( 0 );
3385
3386 wxFileName outFn( aOutput );
3387 outFn.MakeAbsolute();
3388
3389 // Sub-sheets land alongside the root by basename. Preserving the original
3390 // relative-path subdirectory structure would force kicad-cli sch merge to
3391 // mkdir into user space; the basename-flat scheme is what makes the common
3392 // git-mergetool case work without surprises.
3393 const wxString outDir = outFn.GetPath();
3394
3395 SCH_SCREENS screens( rootSheet );
3396 SCH_SCREEN* rootScreen = rootSheet->GetScreen();
3397
3398 // Detect two sub-sheets sharing a basename (e.g., a/foo.kicad_sch and
3399 // b/foo.kicad_sch) before any I/O — the flat output layout can't honor
3400 // both, and silently overwriting one is the worst outcome.
3401 std::map<wxString, SCH_SCREEN*> basenameOwner;
3402
3403 for( size_t i = 0; i < screens.GetCount(); ++i )
3404 {
3405 SCH_SCREEN* screen = screens.GetScreen( i );
3406
3407 if( !screen || screen == rootScreen )
3408 continue;
3409
3410 const wxString basename = wxFileName( screen->GetFileName() ).GetFullName();
3411
3412 if( basename.IsEmpty() )
3413 continue;
3414
3415 auto [it, inserted] = basenameOwner.emplace( basename, screen );
3416
3417 if( !inserted && it->second != screen )
3418 {
3419 m_reporter->Report( wxString::Format( _( "Cannot flatten sub-sheets with duplicate "
3420 "basename '%s'\n" ),
3421 basename ),
3424 }
3425 }
3426
3427 // Rewrite every SCH_SHEET symbol's filename field to its child screen's
3428 // basename, so the root file (and any intermediate sheet) references the
3429 // flattened layout we're about to write.
3430 for( size_t i = 0; i < screens.GetCount(); ++i )
3431 {
3432 SCH_SCREEN* parent = screens.GetScreen( i );
3433
3434 if( !parent )
3435 continue;
3436
3437 for( SCH_ITEM* item : parent->Items().OfType( SCH_SHEET_T ) )
3438 {
3439 SCH_SHEET* childRef = static_cast<SCH_SHEET*>( item );
3440 SCH_SCREEN* childScreen = childRef->GetScreen();
3441
3442 if( !childScreen || childScreen == rootScreen )
3443 continue;
3444
3445 const wxString basename = wxFileName( childScreen->GetFileName() ).GetFullName();
3446
3447 if( !basename.IsEmpty() )
3448 childRef->SetFileName( basename );
3449 }
3450 }
3451
3452 try
3453 {
3454 IO_RELEASER<SCH_IO> pi( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
3455 pi->SaveSchematicFile( outFn.GetFullPath(), rootSheet, ancestor.doc.get() );
3456
3457 for( size_t i = 0; i < screens.GetCount(); ++i )
3458 {
3459 SCH_SCREEN* screen = screens.GetScreen( i );
3460 SCH_SHEET* sheet = screens.GetSheet( i );
3461
3462 if( !screen || !sheet || screen == rootScreen )
3463 continue;
3464
3465 const wxString basename = wxFileName( screen->GetFileName() ).GetFullName();
3466
3467 if( basename.IsEmpty() )
3468 continue;
3469
3470 wxFileName outSubFn( outDir, basename );
3471 pi->SaveSchematicFile( outSubFn.GetFullPath(), sheet, ancestor.doc.get() );
3472 }
3473 }
3474 catch( const IO_ERROR& ioe )
3475 {
3476 m_reporter->Report( wxString::Format( _( "Failed to save merged schematic: %s\n" ), ioe.What() ),
3479 }
3480
3481 // If the applier mutated project-file-scoped state (ERC severities, etc),
3482 // persist it as a sibling .kicad_pro alongside the .kicad_sch output —
3483 // otherwise the resolution dies with the process. Only write when actually
3484 // needed; clobbering an existing .kicad_pro with ancestor's project
3485 // would lose unrelated user settings (library tables, mru paths).
3486 if( applier.GetReport().projectFileTouched && ancestor.project )
3487 {
3488 wxFileName proFn = outFn;
3489 proFn.SetExt( FILEEXT::ProjectFileExtension );
3490
3491 // JSON-patch path: only the diffed DOC_PROP fields are written into
3492 // the output .kicad_pro, so any non-diffed user customisations
3493 // (library tables, last paths, layer presets, text variables) are
3494 // preserved. Fall back to SaveProjectCopy on parse failure.
3495 PROJECT_FILE& ancProj = ancestor.project->GetProjectFile();
3496 ancProj.Store();
3497
3498 const KICAD_DIFF::SCH_MERGE_APPLIER::REPORT& mergeReport = applier.GetReport();
3499
3500 // PROJECT_FILE::Store() flushes the project file's own params but not
3501 // its registered NESTED_SETTINGS. Flush only the resolved nested
3502 // settings so Internals() reflects the merge result without touching
3503 // unrelated project subtrees.
3504 if( mergeReport.ercSeveritiesTouched && ancestor.doc )
3505 ancestor.doc->ErcSettings().SaveToFile( wxEmptyString, true );
3506
3507 if( mergeReport.drawingSheetFileTouched && ancestor.doc )
3508 ancestor.doc->Settings().SaveToFile( wxEmptyString, true );
3509
3510 std::set<wxString> touched;
3511 if( mergeReport.ercSeveritiesTouched )
3512 touched.insert( KICAD_DIFF::DOC_PROP_ERC_SEVERITIES );
3513
3514 if( mergeReport.drawingSheetFileTouched )
3515 touched.insert( KICAD_DIFF::DOC_PROP_DRAWING_SHEET );
3516
3517 if( !KICAD_DIFF::ApplyProjectFilePatches( proFn.GetFullPath(), *ancProj.Internals(), touched,
3519 {
3520 if( !Pgm().GetSettingsManager().SaveProjectCopy( proFn.GetFullPath(), ancestor.project ) )
3521 {
3522 m_reporter->Report(
3523 wxString::Format( _( "Failed to save merged project file: %s\n" ), proFn.GetFullPath() ),
3526 }
3527 }
3528 }
3529
3530 // Surface post-apply validator findings (refdes collisions, schema
3531 // mismatch, missed connectivity rebuild). Advisory — they do not change the
3532 // exit code, only the merge's resolved/unresolved status does.
3534 m_reporter->Report( wxString::Format( wxS( "%s: %s\n" ), f.validator, f.message ), f.severity );
3535
3536 // The merged schematic was written to m_outputPath above, so the output is
3537 // always valid. Unresolved conflicts are reported and signalled via the
3538 // exit code; the user resolves them with the interactive mergetool.
3539 if( !planSnapshot.Resolved() )
3540 {
3541 m_reporter->Report( wxString::Format( _( "Merge completed with %zu unresolved conflict(s) in %s\n" ),
3542 planSnapshot.ConflictCount(), aOutput ),
3545 }
3546
3548}
3549
3550
3551// ============================================================================
3552// JobSymLibMerge: 3-way merge of .kicad_sym libraries.
3553// ============================================================================
3556#include <wx/ffile.h>
3557
3558
3559int EESCHEMA_JOBS_HANDLER::runSymLibMerge( const wxString& aAncestor, const wxString& aOurs,
3560 const wxString& aTheirs, const wxString& aOutput )
3561{
3562 if( aOutput.IsEmpty() )
3563 {
3564 m_reporter->Report( _( "--output is required\n" ), RPT_SEVERITY_ERROR );
3566 }
3567
3568 // Three sides into name -> LIB_SYMBOL maps.
3569 struct LIB_SIDE
3570 {
3571 std::vector<std::unique_ptr<LIB_SYMBOL>> owners;
3573 };
3574
3575 LIB_SIDE ancestor, ours, theirs;
3576
3577 auto loadSide = [&]( const wxString& aPath, LIB_SIDE& aSide ) -> int
3578 {
3579 try
3580 {
3581 auto loaded = KICAD_DIFF::SYM_LIB_DIFFER::LoadLibrary( aPath );
3582 aSide.owners = std::move( loaded.first );
3583 aSide.map = std::move( loaded.second );
3585 }
3586 catch( const IO_ERROR& ioe )
3587 {
3588 m_reporter->Report( wxString::Format( _( "Failed to load %s: %s\n" ), aPath, ioe.What() ),
3590 }
3591 catch( const std::exception& e )
3592 {
3593 m_reporter->Report(
3594 wxString::Format( _( "Failed to load %s: %s\n" ), aPath, wxString::FromUTF8( e.what() ) ),
3596 }
3597
3599 };
3600
3601 if( int rc = loadSide( aAncestor, ancestor ); rc != CLI::EXIT_CODES::SUCCESS )
3602 return rc;
3603
3604 if( int rc = loadSide( aOurs, ours ); rc != CLI::EXIT_CODES::SUCCESS )
3605 return rc;
3606
3607 if( int rc = loadSide( aTheirs, theirs ); rc != CLI::EXIT_CODES::SUCCESS )
3608 return rc;
3609
3610 KICAD_DIFF::SYM_LIB_DIFFER ourDiff( ancestor.map, ours.map, aOurs );
3611 KICAD_DIFF::SYM_LIB_DIFFER theirDiff( ancestor.map, theirs.map, aTheirs );
3612
3613 KICAD_DIFF::DOCUMENT_DIFF ourDocDiff = ourDiff.Diff();
3614 KICAD_DIFF::DOCUMENT_DIFF theirDocDiff = theirDiff.Diff();
3615
3617 KICAD_DIFF::MERGE_PLAN plan = engine.Plan( ourDocDiff, theirDocDiff );
3618
3619 const KICAD_DIFF::MERGE_PLAN planSnapshot = plan;
3620
3621 KICAD_DIFF::SYM_LIB_MERGE_APPLIER applier( ancestor.map, ours.map, theirs.map, std::move( plan ) );
3622 std::vector<std::unique_ptr<LIB_SYMBOL>> merged = applier.Apply();
3623
3624 // Per-property symbol merge isn't implemented; MERGE_PROPS resolutions are
3625 // downgraded to TAKE_OURS. Surface that as unresolved so the user sees a
3626 // marker instead of silent partial-merge.
3627 const bool hadSilentFallback = applier.GetReport().mergePropsFallback > 0;
3628
3629 // Serialize via the sexpr lib cache: create at output path, add each
3630 // merged symbol, save. The cache owns its symbols once added.
3631 wxFileName outFn( aOutput );
3632 outFn.MakeAbsolute();
3633
3634 try
3635 {
3636 SCH_IO_KICAD_SEXPR_LIB_CACHE cache( outFn.GetFullPath() );
3637
3638 // SCH_IO_LIB_CACHE::AddSymbol takes ownership; the cache destructor
3639 // deletes the symbols it holds.
3640 for( auto& sym : merged )
3641 {
3642 if( sym )
3643 cache.AddSymbol( std::move( sym ) );
3644 }
3645
3646 cache.SetModified( true );
3647 cache.Save();
3648 }
3649 catch( const IO_ERROR& ioe )
3650 {
3651 m_reporter->Report( wxString::Format( _( "Failed to save merged symbol library: %s\n" ), ioe.What() ),
3654 }
3655
3656 // The merged library was saved above, so the output is always valid.
3657 if( !planSnapshot.Resolved() || hadSilentFallback )
3658 {
3659 // Conflict count = engine-unresolved ∪ applier-downgraded (deduped, so
3660 // an item that was both unresolved and silently downgraded counts once).
3661 std::set<KIID_PATH> conflicts( planSnapshot.unresolved.begin(), planSnapshot.unresolved.end() );
3662
3663 for( const KIID_PATH& id : applier.GetReport().mergePropsFallbackIds )
3664 conflicts.insert( id );
3665
3666 m_reporter->Report( wxString::Format( _( "Symbol library merge completed with %zu unresolved "
3667 "conflict(s) in %s\n" ),
3668 conflicts.size(), aOutput ),
3671 }
3672
3674}
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:130
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
int GetPageCount() const
Definition base_screen.h:68
int GetVirtualPageNumber() const
Definition base_screen.h:71
const wxString & GetPageNumber() const
Color settings are a bit different than most of the settings objects in that there can be more than o...
What an export to a given target will keep, approximate, drop, or block.
void Add(DOWNGRADE_BUCKET aBucket, const wxString &aFeature, const wxString &aDetail=wxEmptyString, int aCount=1)
void Print(REPORTER *aReporter) const
void Merge(const COMPATIBILITY_REPORT &aOther)
bool IsLossy() const
True if anything is approximated or dropped. This is what triggers the lossy warning.
bool IsBlocked() const
True if some feature makes the target impossible. The user cannot export anyway.
File-compare dialog (Phase 7).
Resolve a three-way merge by choosing a version for each conflicting item.
const KICAD_DIFF::MERGE_PLAN & GetResolvedPlan() const
Returns the plan with the user's resolutions applied.
SIDE
Which side's version the canvas should currently display.
int ShowModal() override
Stage related design and library files together, retaining originals until every write is ready.
const wxString & GetRecoveryError() const
Recovery details are nonempty only if restoring an original or removing a new output failed.
wxString Commit()
Replace all destinations, rolling back earlier replacements if a rename fails.
wxString Stage(const wxString &aDestination)
Return an exclusively-created sibling staging file, or empty if it cannot be created.
bool Create(const wxString &aDirectory, bool aOwnContents=false)
static DS_DATA_MODEL & GetTheInstance()
Return the instance of DS_DATA_MODEL used in the application.
void SetSheetPath(const std::string &aSheetPath)
Set the sheet path displayed in the title block.
void SetSheetCount(int aSheetCount)
Change the sheet-count number displayed in the title block.
void SetVariantName(const std::string &aVariant)
Set the current variant name and description to be shown on the drawing sheet.
void SetVariantDesc(const std::string &aVariantDesc)
void SetPageNumber(const std::string &aPageNumber)
Change the page number displayed in the title block.
void SetSheetName(const std::string &aSheetName)
Set the sheet name displayed in the title block.
void SetPageBorderColorLayer(int aLayerId)
Override the layer used to pick the color of the page border (normally LAYER_GRID)
void SetIsFirstPage(bool aIsFirstPage)
Change if this is first page.
void SetFileName(const std::string &aFileName)
Set the file name displayed in the title block.
void SetColorLayer(int aLayerId)
Can be used to override which layer ID is used for drawing sheet item colors.
const KIID m_Uuid
Definition eda_item.h:599
static SCHEMATIC * LoadSchematic(const wxString &aFileName, bool aSetActive, bool aForceDefaultProject, PROJECT *aProject=nullptr, bool aCalculateConnectivity=true, REPORTER *aRootReporter=nullptr)
int runSchMerge(const wxString &aAncestor, const wxString &aOurs, const wxString &aTheirs, const wxString &aOutput, bool aInteractive)
void InitRenderSettings(SCH_RENDER_SETTINGS *aRenderSettings, const wxString &aTheme, SCHEMATIC *aSch, const wxString &aDrawingSheetOverride=wxEmptyString)
Configure the SCH_RENDER_SETTINGS object with the correct data to be used with plotting.
int RunMerge(KICAD_DIFF::DOC_KIND aKind, const wxString &aAncestor, const wxString &aOurs, const wxString &aTheirs, const wxString &aOutput, bool aInteractive, bool aSingleFile, REPORTER *aReporter)
Non-job entry points (reached via the kiface KIFACE_MERGE_DOCUMENT / KIFACE_OPEN_DIFF_DIALOG function...
DS_PROXY_VIEW_ITEM * getDrawingSheetProxyView(SCHEMATIC *aSch)
KICAD_DIFF::DIFF_CANVAS_CONTEXT * AttachDiffCanvases(KICAD_DIFF::DOC_KIND aKind, const wxString &aFileA, const wxString &aFileB, WIDGET_DIFF_CANVAS *aBefore, WIDGET_DIFF_CANVAS *aAfter, KICAD_DIFF::DOCUMENT_DIFF &aDiff, REPORTER *aReporter)
Diff two schematics for a window outside the kiface, drawing the first on aBefore and the second on a...
int runSymLibMerge(const wxString &aAncestor, const wxString &aOurs, const wxString &aTheirs, const wxString &aOutput)
SCHEMATIC * getSchematic(const wxString &aPath, bool aRequireRoot=true)
int OpenDiffDialog(KICAD_DIFF::DOC_KIND aKind, const wxString &aFileA, const wxString &aFileB, const wxString &aLabelA, const wxString &aLabelB, wxWindow *aParent, REPORTER *aReporter)
void ClearCachedSchematic()
Clear the cached CLI schematic so the next job reloads from the current project.
int doSymExportSvg(JOB_SYM_EXPORT_SVG *aSvgJob, SCH_RENDER_SETTINGS *aRenderSettings, LIB_SYMBOL *symbol)
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:248
bool WriteJsonReport(const wxString &aFullFileName)
Writes a JSON formatted ERC Report to the given file path in the c-locale.
bool WriteTextReport(const wxString &aFullFileName)
Writes the text report also available via GetTextReport directly to a given file path.
Container for ERC settings.
Runs the electrical rules checks and adds a SCH_MARKER for each violation.
Definition erc.h:60
void RunTests(DS_PROXY_VIEW_ITEM *aDrawingSheet, SCH_EDIT_FRAME *aEditFrame, KIFACE *aCvPcb, PROJECT *aProject, PROGRESS_REPORTER *aProgressReporter)
Definition erc.cpp:3395
void SetVariantNames(const std::vector< wxString > &aVariantNames)
std::vector< BOM_FIELD > GetFieldsOrdered()
static const wxString ITEM_NUMBER_VARIABLE
wxString Export(const BOM_FMT_PRESET &aSettings)
int GetFieldNameCol(const wxString &aFieldName) const
void ApplyBomPreset(const BOM_PRESET &aPreset)
void AddColumn(const wxString &aFieldName, const wxString &aLabel, bool aAddedByUser) override
void SetCurrentVariant(const wxString &aVariantName) override
Select the displayed variant, retaining un-applied edits in every variant.
Reporter handed to an importer during an import job.
void Flush(IMPORT_REPORT_FORMAT aFormat, IMPORT_REPORT_DATA &aData)
Move the collected messages into aData when a report will be written, otherwise forward them to the j...
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
wxString m_inputB
Comparison document (file or directory)
wxString m_inputA
Reference document (file or directory)
void Register(const std::string &aJobTypeName, std::function< int(JOB *job)> aHandler, std::function< bool(JOB *job, wxWindow *aParent)> aConfigHandler)
JOB_DISPATCHER(KIWAY *aKiway)
PROGRESS_REPORTER * m_progressReporter
REPORTER * m_reporter
wxString GetSelectedVariant() const
std::vector< wxString > m_variantNames
wxString m_stringDelimiter
wxString m_fieldDelimiter
bool m_includeByteOrderMark
wxString m_filename
wxString m_filterString
std::vector< wxString > m_fieldsOrdered
wxString m_refRangeDelimiter
std::vector< wxString > m_fieldsLabels
wxString m_refDelimiter
std::vector< wxString > m_fieldsGroupBy
wxString m_bomFmtPresetName
wxString m_sortField
wxString m_bomPresetName
BOM_FILTER_SCOPE m_filterScope
std::vector< wxString > m_variantNames
JOB_PAGE_SIZE m_pageSizeSelect
SCH_PLOT_FORMAT m_plotFormat
std::vector< wxString > m_variantNames
std::vector< wxString > m_plotPages
bool m_exitCodeViolations
Definition job_rc.h:52
int m_severity
Definition job_rc.h:49
UNITS m_units
Definition job_rc.h:48
OUTPUT_FORMAT m_format
Definition job_rc.h:50
wxString m_filename
Definition job_rc.h:47
std::vector< wxString > m_foundVariants
Variant names found in the schematic when the target drops variants.
wxString m_outputDir
Write downgraded sheets under this fresh folder.
wxString m_variant
Flatten this variant into the export. Empty means the base design.
wxString m_libraryDir
If set, downgrade every .kicad_sym symbol library under this folder too.
bool m_inPlace
Internal only: convert the coordinator's already-copied export files.
bool m_dryRun
Only classify and fill m_report, do not write anything.
wxString m_filename
Root schematic file (may be empty to downgrade only libraries)
COMPATIBILITY_REPORT m_report
Filled by the handler so the caller can show it.
wxString m_target
Target release name or id, e.g. "KiCad 10.0" or "10.0". Empty means the newest.
bool m_dropInsteadOfApproximate
Omit features that would otherwise be approximated.
bool m_force
Export even when features must be dropped or approximated.
Job to import a non-KiCad schematic file to KiCad format.
wxString m_reportFile
wxString m_inputFile
IMPORT_REPORT_FORMAT m_reportFormat
std::map< wxString, wxString > m_netNameMap
wxString m_outputLibraryPath
An simple container class that lets us dispatch output jobs to kifaces.
Definition job.h:184
void SetConfiguredOutputPath(const wxString &aPath)
Sets the configured output path for the job, this path is always saved to file.
Definition job.cpp:156
void AddOutput(wxString aOutputPath)
Definition job.h:216
wxString GetFullOutputPath(PROJECT *aProject) const
Returns the full output path for the job, taking into account the configured output path,...
Definition job.cpp:149
bool GetOutputPathIsDirectory() const
Definition job.h:258
wxString GetConfiguredOutputPath() const
Returns the configured output path for the job.
Definition job.h:235
void SetTitleBlock(const TITLE_BLOCK &aTitleBlock)
Definition job.h:204
void SetWorkingOutputPath(const wxString &aPath)
Sets a transient output path for the job, it takes priority over the configured output path when GetF...
Definition job.h:241
const std::map< wxString, wxString > & GetVarOverrides() const
Definition job.h:197
std::optional< nlohmann::json > GetJson(const std::string &aPath) const
Fetches a JSON object that is a subset of this JSON_SETTINGS object, using a path of the form "key1....
JSON_SETTINGS_INTERNALS * Internals()
Keeps the documents drawn on a pair of diff canvases alive.
Three-way merge plan generator.
MERGE_PLAN Plan(const DOCUMENT_DIFF &aAncestorOurs, const DOCUMENT_DIFF &aAncestorTheirs) const
Plan the merge given the canonical pair of diffs.
const REPORT & GetReport() const
std::vector< std::unique_ptr< ITEM > > Apply()
Diff two already-parsed SCHEMATICs and produce a DOCUMENT_DIFF.
Definition sch_differ.h:55
DOCUMENT_DIFF Diff() override
Produce a DOCUMENT_DIFF of the inputs the concrete differ was constructed with.
Materialize a MERGE_PLAN into a merged SCHEMATIC by mutating the ancestor in place.
bool Apply()
Apply the plan to the ancestor.
const REPORT & GetReport() const
Diff two .kicad_sym symbol libraries.
DOCUMENT_DIFF Diff() override
Produce a DOCUMENT_DIFF of the inputs the concrete differ was constructed with.
static std::pair< std::vector< std::unique_ptr< LIB_SYMBOL > >, SYMBOL_MAP > LoadLibrary(const wxString &aPath)
Convenience: load a .kicad_sym path into a SYMBOL_MAP using SCH_IO_KICAD_SEXPR::EnumerateSymbolLib.
std::map< wxString, const LIB_SYMBOL * > SYMBOL_MAP
Library content is a map of (canonical_name -> LIB_SYMBOL*).
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
void SetDefaultPenWidth(int aWidth)
void SetGapLengthRatio(double aRatio)
void SetDashLengthRatio(double aRatio)
Definition kiid.h:46
A minimalistic software bus for communications between various DLLs/DSOs (DSOs) within the same KiCad...
Definition kiway.h:348
virtual KIWAY_PLAYER * Player(FRAME_T aFrameType, bool doCreate=true, wxTopLevelWindow *aParent=nullptr)
Return the KIWAY_PLAYER* given a FRAME_T.
Definition kiway.cpp:401
@ FACE_CVPCB
Definition kiway.h:357
void AsyncLoad()
Loads all available libraries for this adapter type in the background.
Define a library symbol object.
Definition lib_symbol.h:114
bool IsDerived() const
Definition lib_symbol.h:241
std::shared_ptr< LIB_SYMBOL > GetRootSymbol() const
Get the parent symbol that does not have another parent.
wxString GetName() const override
Definition lib_symbol.h:183
const std::vector< wxString > & GetBodyStyleNames() const
bool HasDeMorganBodyStyles() const override
int GetBodyStyleCount() const override
The body styles are a property of the drawings, which a derived symbol inherits from its root symbol ...
int GetUnitCount() const override
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:37
static bool EnsurePathExists(const wxString &aPath, bool aPathToFile=false)
Attempts to create a given path if it does not exist.
Definition paths.cpp:470
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition pgm_base.h:123
The backing store for a PROJECT, in JSON format.
ERC_SETTINGS * m_ErcSettings
Eeschema params.
SCHEMATIC_SETTINGS * m_SchematicSettings
TEMPLATES m_TemplateFieldNames
Project and global field name templates shared by project editors.
bool Store() override
Stores the current parameters into the JSON document represented by this object Note: this doesn't do...
std::vector< TOP_LEVEL_SHEET_INFO > & GetTopLevelSheets()
static SYMBOL_LIBRARY_ADAPTER * SymbolLibAdapter(PROJECT *aProject)
Accessor for project symbol library manager adapter.
Container for project specific data.
Definition project.h:63
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition project.cpp:276
virtual void ApplyTextVars(const std::map< wxString, wxString > &aVarsMap)
Applies the given var map, it will create or update existing vars.
Definition project.cpp:228
virtual PROJECT_FILE & GetProjectFile() const
Definition project.h:201
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:102
These are loaded from Eeschema settings but then overwritten by the project settings.
Holds all the data relating to one schematic.
Definition schematic.h:149
void SetCurrentVariant(const wxString &aVariantName)
void DeleteVariant(const wxString &aVariantName, SCH_COMMIT *aCommit=nullptr)
Delete all information for aVariantName.
wxString GetVariantDescription(const wxString &aVariantName) const
Return the description for a variant.
void LoadVariants()
This is a throw away method for variant testing.
SCH_SHEET_LIST BuildSheetListSortedByPageNumbers() const
const std::vector< std::shared_ptr< BUS_ALIAS > > & GetAllBusAliases() const
Definition schematic.h:357
wxString GetFileName() const
Helper to retrieve the filename from the root sheet screen.
SCHEMATIC_SETTINGS & Settings() const
SCH_SHEET_LIST Hierarchy() const
Return the full schematic flattened hierarchical sheet list.
SCH_SHEET_LIST BuildUnorderedSheetList() const
PROJECT & Project() const
Return a reference to the project this schematic is part of.
Definition schematic.h:171
void SetProject(PROJECT *aPrj)
wxString GetCurrentVariant() const
Return the current variant being edited.
SCH_SCREEN * RootScreen() const
Helper to retrieve the screen of the root sheet.
void SetTopLevelSheets(const std::vector< SCH_SHEET * > &aSheets)
Replace the top level sheets, rebuilding the hierarchy and connectivity around them.
const std::set< wxString > & GetVariantNames() const
Return the set of variant names (without the default placeholder).
Definition schematic.h:664
const std::map< wxString, wxString > * GetProperties()
Definition schematic.h:174
SCH_SHEET & Root() const
Definition schematic.h:208
std::vector< SCH_SHEET * > GetTopLevelSheets() const
Get the list of top-level sheets.
Commit for the schematic and symbol editors.
Definition sch_commit.h:54
void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
SCH_DIFF_CANVAS_CONTEXT(WIDGET_DIFF_CANVAS *aBefore, WIDGET_DIFF_CANVAS *aAfter)
Schematic editor (Eeschema) main window.
SCHEMATIC & Schematic() const
A cache assistant for the KiCad s-expression symbol libraries.
void Save(const std::optional< bool > &aOpt=std::nullopt) override
Save the entire library to file m_libFileName;.
A SCH_IO derivation for loading schematic files using the new s-expression file format.
void EnumerateSymbolLib(wxArrayString &aSymbolNameList, const wxString &aLibraryPath, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Populate a list of LIB_SYMBOL alias names contained within the library aLibraryPath.
void SetFileName(const wxString &aFileName)
void SetModified(bool aModified=true)
virtual void AddSymbol(std::unique_ptr< LIB_SYMBOL > aSymbol)
static bool ConvertLibrary(std::map< std::string, UTF8 > *aOldFileProps, const wxString &aOldFilePath, const wxString &aNewFilepath, REPORTER *aReporter=nullptr)
Convert a schematic symbol library to the latest KiCad format.
static const wxString ShowType(SCH_FILE_T aFileType)
Return a brief name for a plugin, given aFileType enum.
static SCH_FILE_T GuessPluginTypeFromSchPath(const wxString &aSchematicPath, int aCtl=0)
Return a plugin type given a schematic using the file extension of aSchematicPath.
static SCH_FILE_T GuessPluginTypeFromLibPath(const wxString &aLibPath, int aCtl=0)
Return a plugin type given a symbol library using the file extension of aLibPath.
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:170
Container to create a flattened list of symbols because in a complex hierarchy, a symbol can be used ...
int CheckAnnotation(ANNOTATION_ERROR_HANDLER aErrorHandler)
Check for annotations errors.
A helper to define a symbol's reference designator in a schematic.
void LoadColors(const COLOR_SETTINGS *aSettings) override
Container class that holds multiple SCH_SCREEN objects in a hierarchy.
Definition sch_screen.h:826
SCH_SCREEN * GetNext()
SCH_SCREEN * GetScreen(unsigned int aIndex) const
SCH_SCREEN * GetFirst()
size_t GetCount() const
Definition sch_screen.h:831
SCH_SHEET * GetSheet(unsigned int aIndex) const
const PAGE_INFO & GetPageSettings() const
Definition sch_screen.h:144
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:122
const wxString & GetFileName() const
Definition sch_screen.h:157
int GetFileFormatVersionAtLoad() const
Definition sch_screen.h:142
TITLE_BLOCK & GetTitleBlock()
Definition sch_screen.h:168
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
std::optional< SCH_SHEET_PATH > GetSheetPathByKIIDPath(const KIID_PATH &aPath, bool aIncludeLastSheet=true) const
Finds a SCH_SHEET_PATH that matches the provided KIID_PATH.
void GetSymbols(SCH_REFERENCE_LIST &aReferences, SYMBOL_FILTER aSymbolFilter, bool aForceIncludeOrphanSymbols=false) const
Add a SCH_REFERENCE object to aReferences for each symbol in the list of sheets.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:48
void SetFileName(const wxString &aFilename)
Definition sch_sheet.h:398
wxString GetName() const
Definition sch_sheet.h:148
void SetName(const wxString &aName)
Definition sch_sheet.h:149
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:151
bool IsVirtualRootSheet() const
Schematic symbol object.
Definition sch_symbol.h:75
void GetFields(std::vector< SCH_FIELD * > &aVector, bool aVisibleOnly) const override
Populate a std::vector with SCH_FIELDs, sorted in ordinal order.
RAII class that sets an value at construction and resets it to the original value at destruction.
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
PROJECT * GetProject(const wxString &aFullPath) const
Retrieve a loaded project by name.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
bool IsProjectOpenNotDummy() const
Helper for checking if we have a project open that is not a dummy project.
void UpdateReferences(const SCH_REFERENCE_LIST &aRefs)
const SCH_REFERENCE_LIST & GetReferenceList() const
An interface to the global shared library manager that is schematic-specific and linked to one projec...
const std::vector< TEMPLATE_FIELDNAME > & GetResolvedTemplateFieldNames()
Return the resolved project and global template field name list for read only access.
GAL-backed canvas for visualizing a KICAD_DIFF::DIFF_SCENE.
wxString GetGeneratedFieldDisplayName(const wxString &aSource)
Returns any variables unexpanded, e.g.
Definition common.cpp:481
bool IsGeneratedField(const wxString &aFieldName)
Returns true if the entire string is generated, e.g is a single text var reference.
Definition common.cpp:494
wxString GetDefaultPlotExtension(PLOT_FORMAT aFormat)
Return the default plot extension for a format.
@ DROP
No equivalent, left out.
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:217
This file is part of the common library.
KIFACE_BASE & Kiface()
Global KIFACE_BASE "get" accessor.
Definition cvpcb.cpp:165
#define DEFAULT_LINE_WIDTH_MILS
The default line width in mils. (can be changed in preference menu)
static bool empty(const wxTextEntryBase *aCtrl)
wxString DowngradeWxPath(const std::filesystem::path &aPath)
wxString DowngradeLibraryCopyPath(const wxString &aLibrary, const wxString &aOutputDirectory)
bool ValidateDowngradeLibrarySource(const wxString &aSource, wxString &aError)
bool ValidateDowngradeLibraryCopy(const wxString &aSource, const wxString &aDestination, wxString &aError)
bool ValidateFreshDowngradeOutput(const wxString &aOutput, wxString &aError)
bool CopyDowngradeLibraryDirectory(const wxString &aSource, const wxString &aDestination, DOWNGRADE_OUTPUT_DIRECTORIES &aDirectories, wxString &aError)
std::filesystem::path DowngradeNativePath(const wxString &aPath)
wxString DowngradeLibraryFilesInPlace(const wxString &aDir, const wxString &aGlob, int aStampVersion, const std::function< wxString(const wxString &)> &aFindForbidden, const std::function< DOWNGRADE_FILE_RESULT(const wxString &, const wxString &)> &aConvert, DOWNGRADE_FILE_TRANSACTION *aTransaction=nullptr, wxString *aRecoveryError=nullptr)
Downgrade every library file matching aGlob under aDir in place.
bool IsDowngradeWorkingArtifact(const wxString &aPath)
Editor working state (autosaves, local history, backups) holds current-format copies nothing downgrad...
DOWNGRADE_FILE_RESULT
What a per-file converter did with one library file.
@ CONVERTED
The temp file holds the downgraded copy.
bool VerifyDowngradedFile(const wxString &aPath, int aStampVersion, const std::function< wxString(const wxString &)> &aFindForbidden, wxString *aForbidden=nullptr)
Read back a downgraded output and require the target stamp plus a clean token scan.
const std::vector< DOWNGRADE_TARGET > & GetDowngradeTargets()
Supported downgrade targets, newest first.
const DOWNGRADE_TARGET * FindDowngradeTarget(const wxString &aNameOrId)
Match a user-supplied target string against name or stable id, case-insensitively.
wxString DowngradeTargetNames()
The valid target choices as one user-facing list, for error messages.
#define _(s)
EDA_UNITS
Definition eda_units.h:44
bool PrepareSaveAsFiles(SCHEMATIC &aSchematic, SCH_SCREENS &aScreens, const wxFileName &aOldRoot, const wxFileName &aNewRoot, bool aSaveCopy, bool aCopySubsheets, bool aIncludeExternSheets, std::unordered_map< SCH_SCREEN *, wxString > &aFilenameMap, wxString &aErrorMsg)
static int loadSchDiffSides(SETTINGS_MANAGER &aMgr, REPORTER &aReporter, const wxString &aFileA, const wxString &aFileB, SCH_DIFF_SIDES &aSides)
static wxString downgradeSymbolLibraries(const wxString &aDir, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate, DOWNGRADE_FILE_TRANSACTION *aTransaction=nullptr, wxString *aRecoveryError=nullptr)
static SCRATCH_DOC< SCHEMATIC > emptyScratchSchematic(SETTINGS_MANAGER &aMgr, const wxString &aTag)
static int loadSymbolLibrarySide(const wxString &aPath, std::vector< std::unique_ptr< LIB_SYMBOL > > &aOwners, KICAD_DIFF::SYM_LIB_DIFFER::SYMBOL_MAP &aMap, bool aAllowEmpty, REPORTER &aReporter)
static SCRATCH_DOC< SCHEMATIC > loadScratchSchematic(SETTINGS_MANAGER &aMgr, const wxString &aPath)
static void classifySymbolLibraries(const wxString &aDir, const DOWNGRADE_TARGET &aTarget, COMPATIBILITY_REPORT &aReport, bool aDropInsteadOfApproximate)
static KICAD_DIFF::DOCUMENT_GEOMETRY symbolLibraryGeometry(const KICAD_DIFF::SYM_LIB_DIFFER::SYMBOL_MAP &aMap, const KIGFX::COLOR4D &aColor)
ERCE_T
ERC error codes.
@ FRAME_SCH
Definition frame_type.h:30
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
static const std::string CadstarNetlistFileExtension
static const std::string NetlistFileExtension
static const std::string ReportFileExtension
static const std::string ProjectFileExtension
static const std::string JsonFileExtension
static const std::string XmlFileExtension
static const std::string KiCadSchematicFileExtension
static const std::string OrCadPcb2NetlistFileExtension
static const std::string CsvFileExtension
static const std::string SpiceFileExtension
static const std::string SVGFileExtension
std::string source
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition idf_helpers.h:37
std::map< wxString, wxString > GetBoardNetNameMap(const IMPORT_NET_MAP &aMap, REPORTER &aReporter)
Reduce the map to the board net renames it justifies.
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
Definition io_mgr.h:33
wxString DefaultImportOutputPath(const wxString &aInputFile, const wxString &aKiCadExt)
Build the default output path for an import by swapping the input file's extension for the given KiCa...
bool WriteImportReport(REPORTER *aReporter, IMPORT_REPORT_FORMAT aFormat, const wxString &aReportFile, const IMPORT_REPORT_DATA &aData)
Emit an import report in the requested format to aReportFile, or to aReporter (at INFO severity) when...
#define KICAD_FONT_NAME
KIID niluuid(0)
#define KICTL_NONKICAD_ONLY
chosen file is non-KiCad according to user
@ LAYER_SCHEMATIC_DRAWINGSHEET
Definition layer_ids.h:524
@ LAYER_SCHEMATIC_PAGE_LIMITS
Definition layer_ids.h:525
static std::vector< PENDING_PROPERTY > plan(const EDA_ITEM &aSource, const EDA_ITEM &aTarget, const std::set< wxString > &aEnabledKeys)
Target properties to write, paired with values read off the source.
static const int ERR_ARGS
Definition exit_codes.h:31
static const int OK
Definition exit_codes.h:30
static const int ERR_RC_VIOLATIONS
Rules check violation count was greater than 0.
Definition exit_codes.h:37
static const int ERR_CANCELLED
The user cancelled the operation.
Definition exit_codes.h:45
static const int ERR_INVALID_INPUT_FILE
Definition exit_codes.h:33
static const int SUCCESS
Definition exit_codes.h:29
static const int ERR_INVALID_OUTPUT_CONFLICT
Definition exit_codes.h:34
static const int ERR_UNKNOWN_FILE_FORMAT
No plugin for the requested face recognized the input file format.
Definition exit_codes.h:42
static const int ERR_UNKNOWN
Definition exit_codes.h:32
const wxString DOC_PROP_ERC_SEVERITIES
void ConfigureSchDiffCanvasContext(WIDGET_DIFF_CANVAS &aCanvas, SCHEMATIC *aReference, SCHEMATIC *aComparison, const KIGFX::COLOR4D &aColor, const std::map< KIID, KIGFX::COLOR4D > &aOverrides, const std::vector< KIGFX::VIEW_ITEM * > &aExtraItems, const std::map< KIID, KICAD_DIFF::CATEGORY > &aCategories, SCH_SCREEN *aReferenceScreen, SCH_SCREEN *aComparisonScreen)
std::vector< CONFLICT_LIST_ENTRY > BuildConflictList(const MERGE_PLAN &aPlan)
void CollectChangeBBoxes(const DOCUMENT_DIFF &aDiff, std::map< KIID_PATH, BOX2I > &aOut)
Walk a DOCUMENT_DIFF and populate a (KIID_PATH → BOX2I) map with each changed item's bbox,...
DOCUMENT_GEOMETRY ExtractSymbolGeometry(const LIB_SYMBOL &aSymbol, const KIGFX::COLOR4D &aColor, int aUnit, int aBodyStyle)
Extract coarse drawable context from a library symbol for visual symbol diffs.
DOCUMENT_GEOMETRY ExtractSchematicGeometry(const SCHEMATIC &aSchematic, const KIGFX::COLOR4D &aColor, const std::map< KIID, KIGFX::COLOR4D > &aOverrides, bool aOnlyOverrides)
Extract a coarse outline of a SCHEMATIC into a DOCUMENT_GEOMETRY for use as background context in DIF...
wxString OlderFormatNotice(int aAncestorVersion, int aOursVersion, int aTheirsVersion, int aCurrentVersion)
Empty when every side is already in the current file format.
void AppendGeometry(DOCUMENT_GEOMETRY &aDst, DOCUMENT_GEOMETRY &&aSrc)
Move all primitives from aSrc into aDst.
DIFF_EMIT_OPTIONS MakeEmitOptions(const JOB_DIFF_BASE &aJob, const wxString &aLabelA, const wxString &aLabelB)
Build a DIFF_EMIT_OPTIONS pre-filled from the job's format, resolved output path and the supplied per...
const wxString DOC_PROP_DRAWING_SHEET
DOC_KIND
Document type a diff/merge entry point should route to, derived from a file path's extension.
int EmitDiffResult(const DOCUMENT_DIFF &aResult, const DIFF_EMIT_OPTIONS &aOptions, int aDiffExitCode, REPORTER &aReporter)
Emit a computed DOCUMENT_DIFF in the requested format.
std::unique_ptr< KIGFX::PAINTER > MakeSchDiffContextPainter(KIGFX::GAL *aGal, SCHEMATIC *aSchematic, const KIGFX::COLOR4D &aColor, std::map< KIID, KIGFX::COLOR4D > aOverrides)
LIB_MERGE_APPLIER< LIB_SYMBOL > SYM_LIB_MERGE_APPLIER
Symbol-library 3-way merge applier. See LIB_MERGE_APPLIER for behavior.
bool ApplyProjectFilePatches(const wxString &aOutputProPath, const nlohmann::json &aSource, const std::set< wxString > &aDocProps, DOC_KIND aKind)
Higher-level orchestrator: load the existing aOutputProPath as JSON (or start from aSource if the fil...
int DiffExitCode(const DOCUMENT_DIFF &aResult)
Map a computed diff onto its CLI exit code – SUCCESS when empty, otherwise ERR_RC_VIOLATIONS.
@ GNL_OPT_BOM
static int plotJob(JOB_EXPORT_PCB_PLOT *aJob, PCB_PLOTTER &aPlotter, const wxString &aOutPath, bool aSingleOutput, bool aUseOverrides)
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
PLOT_FORMAT
The set of supported output plot formats.
Definition plotter.h:65
Plotting engines similar to ps (PostScript, Gerber, svg)
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_INFO
@ RPT_SEVERITY_ACTION
#define SKIP_CONNECTIVITY
Definition sch_commit.h:41
#define DELETE_REMOVED_ITEMS
Definition sch_commit.h:43
#define SKIP_UNDO
Definition sch_commit.h:38
COMPATIBILITY_REPORT ClassifySchematicStructureForDowngrade(const SCHEMATIC &aSchematic, const DOWNGRADE_TARGET &aTarget)
Schematic-wide structure the per-screen rules cannot see. Does not modify it.
void DowngradeScreenInPlace(SCH_SCREEN *aScreen, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate)
Approximate or remove, in place, what the target cannot represent.
bool FlattenSchematicVariant(SCHEMATIC &aSchematic, const wxString &aVariantName, wxString *aConflict)
Bake a variant into the base attributes of every symbol and sheet.
void SaveSymbolLibraryForTarget(const std::vector< LIB_SYMBOL * > &aSymbols, const wxString &aPath, const DOWNGRADE_TARGET &aTarget)
Save a .kicad_sym library stamped for the target.
COMPATIBILITY_REPORT ClassifyLibSymbolForDowngrade(const LIB_SYMBOL *aSymbol, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate)
Report what a library symbol would lose if exported to an older KiCad. Does not modify it.
void DowngradeLibSymbolInPlace(LIB_SYMBOL *aSymbol, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate)
Symbol-scoped downgrade for standalone .kicad_sym files the screen pass never sees.
void SaveSchematicForTarget(SCH_SHEET *aSheet, SCHEMATIC *aSchematic, const wxString &aPath, const DOWNGRADE_TARGET &aTarget)
Save a schematic sheet file stamped for the target, using the writer for that version.
COMPATIBILITY_REPORT ClassifyScreenForDowngrade(const SCH_SCREEN *aScreen, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate)
Report what a schematic screen would lose if exported to an older KiCad.
constexpr int SCH_VER_VARIANTS
Format dates for rules the jobs handler applies at schematic scope.
constexpr int SCH_VER_NET_CHAINS
#define SEXPR_SYMBOL_LIB_FILE_VERSION
This file contains the file format version information for the s-expression schematic and symbol libr...
#define SEXPR_SCHEMATIC_FILE_VERSION
Schematic file version.
bool PlotSymbolToSVG(LIB_SYMBOL &aDrawSymbol, LIB_SYMBOL &aFieldsSymbol, int aUnit, int aBodyStyle, const BOX2I &aBBox, SCH_RENDER_SETTINGS &aRenderSettings, bool aBlackAndWhite, const wxString &aFileName, REPORTER *aReporter)
Plot a single symbol variant (unit and body style) to an SVG file.
BOX2I GetSymbolPlotBBox(const LIB_SYMBOL &aSymbol, int aUnit, int aBodyStyle, bool aIncludeHiddenFields)
Compute the bounding box used to size a symbol SVG plot.
@ PAGE_SIZE_AUTO
Definition sch_plotter.h:47
@ PAGE_SIZE_A
Definition sch_plotter.h:49
@ PAGE_SIZE_A4
Definition sch_plotter.h:48
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
@ SYMBOL_FILTER_NON_POWER
@ GLOBAL_CLEANUP
Definition schematic.h:95
SCRATCH_DOC< DOC > LoadScratchDoc(SETTINGS_MANAGER &aMgr, const wxString &aDocPath, Loader aLoader, ClearFn aClearFn)
Construct a SCRATCH_DOC by loading a project non-active and then handing it to the caller's document ...
COLOR_SETTINGS * GetColorSettings(const wxString &aName)
T * GetAppSettings(const char *aFilename)
std::vector< FAB_LAYER_COLOR > dummy
MODEL3D_FORMAT_TYPE fileType(const char *aFileName)
wxString label
wxString name
wxString fieldDelimiter
bool includeByteOrderMark
static std::vector< BOM_FMT_PRESET > BuiltInPresets()
wxString stringDelimiter
wxString refRangeDelimiter
wxString refDelimiter
Phase 8 context for the conflict canvas.
wxString formatNotice
Shown with the instructions when the merge cannot keep a side's file format.
std::function< void(WIDGET_DIFF_CANVAS &, SIDE, const std::optional< KIID_PATH > &)> drawSide
Draw a side on the canvas with the editor's own painter.
std::function< wxString(const KIID_PATH &)> describeItem
Name an item the way the editor would, so a conflict can be told from its neighbours.
An older KiCad release we can export a project back to.
int m_schVersion
SEXPR_SCHEMATIC_FILE_VERSION for the target.
int m_symLibVersion
SEXPR_SYMBOL_LIB_FILE_VERSION for the target.
wxString m_name
User-facing name, e.g. "KiCad 10.0".
std::vector< BOM_PRESET > m_BomPresets
std::vector< BOM_FMT_PRESET > m_BomFmtPresets
Import provenance, held only for the lifetime of the importing SCHEMATIC.
Diff colors with defaults for light backgrounds.
Definition diff_scene.h:278
KIGFX::COLOR4D reference
Default color for source-document context geometry.
Definition diff_scene.h:292
Describes how a computed DOCUMENT_DIFF should be emitted by a diff job.
std::function< DOCUMENT_GEOMETRY(const KIGFX::COLOR4D &)> comparisonGeometry
DOC_KIND docKind
Source document type, propagated onto the scene so the PNG/SVG renderer sizes its viewport with the m...
std::function< DOCUMENT_GEOMETRY(const KIGFX::COLOR4D &)> referenceGeometry
The full set of changes between two parsed documents of one type.
Aggregate of background geometry extracted from one source document.
Definition diff_scene.h:163
One change record on a single item.
Result of planning a 3-way merge.
std::size_t ConflictCount() const
std::vector< KIID_PATH > unresolved
bool projectFileTouched
True iff the applier resolved state that lives in the .kicad_pro.
std::size_t sheetActionsSkipped
Number of actions skipped because they targeted a SCH_SHEET.
VALIDATION_REPORT validation
Post-apply validator pipeline result.
Outcome of a single validator run.
std::vector< VALIDATION_FAILURE > failures
SCRATCH_DOC< SCHEMATIC > emptyA
KICAD_DIFF::DOCUMENT_DIFF diff
SCRATCH_DOC< SCHEMATIC > b
SCRATCH_DOC< SCHEMATIC > a
SCRATCH_DOC< SCHEMATIC > emptyB
std::vector< wxString > m_plotPages
Definition sch_plotter.h:57
wxString m_theme
Definition sch_plotter.h:69
DXF_UNITS m_DXF_File_Unit
Definition sch_plotter.h:76
bool m_PDFPropertyPopups
Definition sch_plotter.h:66
wxString m_outputDirectory
Definition sch_plotter.h:71
std::optional< SCH_SHEET_PATH > m_sheetPath
Definition sch_plotter.h:60
bool m_pngAntialias
Definition sch_plotter.h:79
wxString m_outputFile
Definition sch_plotter.h:72
bool m_blackAndWhite
Definition sch_plotter.h:63
wxString m_variant
Definition sch_plotter.h:73
bool m_PDFHierarchicalLinks
Definition sch_plotter.h:67
bool m_useBackgroundColor
Definition sch_plotter.h:65
bool m_plotDrawingSheet
Definition sch_plotter.h:56
Move-only RAII wrapper for "load a KiCad document into a non-active scratch PROJECT and clean up afte...
PROJECT * project
std::unique_ptr< DOC > doc
Hold a name of a symbol's field, field value, and default visibility.
Information about a top-level schematic sheet.
SYMBOL_IMPORT_RECONCILE_RESULT ReconcileImportedSymbols(SCH_IO &aPlugin, SCHEMATIC &aSchematic, PROJECT &aProject, const wxString &aSchematicPath, const std::map< std::string, UTF8 > *aProperties, REPORTER &aReporter)
Reconcile aSchematic against the definitions aPlugin retained while loading it.
std::map< wxString, LIB_SYMBOL *, LibSymbolMapSort > LIB_SYMBOL_MAP
Symbol library map sorted by the symbol name.
wxString GetDefaultFieldName(FIELD_T aFieldId, TRANSLATION aTranslation)
Return a default symbol field name for a mandatory field type.
#define MANDATORY_FIELDS
FIELD_T
The set of all field indices assuming an array like sequence that a SCH_COMPONENT or LIB_PART can hol...
@ UNTRANSLATED
@ TRANSLATED
std::string path
VECTOR3I res
wxString result
Test unit parsing edge cases and error handling.
@ SCH_SYMBOL_T
Definition typeinfo.h:168
@ SCH_SHEET_T
Definition typeinfo.h:171
Definition of file extensions used in Kicad.