50bool containsBoardFile(
const std::filesystem::path& aTarget,
const wxString& aBoardFile )
52 namespace fs = std::filesystem;
54 if( aBoardFile.IsEmpty() )
57 fs::path file =
ToFsPath( aBoardFile );
60 if( !fs::exists( file, ec ) )
63 for( fs::path dir = file.parent_path(); !dir.empty(); dir = dir.parent_path() )
65 if( fs::equivalent( aTarget, dir, ec ) )
71 if( dir == dir.root_path() )
85 if( !requested.IsEmpty() )
89 wxString boardName = wxFileName( aBoard->
GetFileName() ).GetName();
91 if( boardName.IsEmpty() )
92 boardName = wxFileName( aJob.
m_filename ).GetName();
96 if( product.IsEmpty() )
97 product = wxS(
"odb" );
105 const wxString& aProductModelName )
131bool canReplaceOdbDirectory(
const wxString& aTarget,
const wxString& aBoardFile,
132 const wxString& aJobFile, wxString& aError )
134 namespace fs = std::filesystem;
135 fs::path target =
ToFsPath( aTarget );
138 if( !fs::exists( target, ec ) )
141 aError = wxString::FromUTF8( ec.message() );
146 if( !fs::is_directory( target, ec ) )
148 aError = ec ? wxString::FromUTF8( ec.message() ) :
_(
"Output path is not a directory" );
152 bool empty = fs::is_empty( target, ec );
156 aError = wxString::FromUTF8( ec.message() );
163 if( !fs::is_regular_file( target /
"matrix" /
"matrix", ec ) )
165 aError =
_(
"Output directory is not an ODB++ product" );
168 aError += wxS(
"\n" ) + wxString::FromUTF8( ec.message() );
173 static const std::array<fs::path, 9> allowed = {
174 fs::path(
"ext" ), fs::path(
"fonts" ), fs::path(
"input" ), fs::path(
"matrix" ), fs::path(
"misc" ),
175 fs::path(
"steps" ), fs::path(
"symbols" ), fs::path(
"user" ), fs::path(
"wheels" )
178 fs::directory_iterator child( target, ec );
179 fs::directory_iterator
end;
181 while( !ec && child !=
end )
183 fs::path
name = child->path().filename();
185 if( std::find( allowed.begin(), allowed.end(),
name ) == allowed.end() )
187 aError = wxString::Format(
_(
"Output directory contains unrelated entry '%s'" ),
FromFsPath(
name ) );
191 child.increment( ec );
196 aError = wxString::FromUTF8( ec.message() );
200 if( containsBoardFile( target, aBoardFile ) || containsBoardFile( target, aJobFile ) )
202 aError =
_(
"Output directory contains the source board" );
206 fs::recursive_directory_iterator nested( target, ec );
207 fs::recursive_directory_iterator nestedEnd;
209 while( !ec && nested != nestedEnd )
211 bool regular = nested->is_regular_file( ec );
215 aError = wxString::FromUTF8( ec.message() );
221 wxString extension =
FromFsPath( nested->path().extension() ).Lower();
223 if( extension.StartsWith( wxS(
".kicad_" ) ) || extension == wxS(
".pro" )
224 || extension == wxS(
".sch" ) || extension == wxS(
".pcb" ) )
226 aError = wxString::Format(
_(
"Output directory contains project file '%s'" ),
232 nested.increment( ec );
236 aError = wxString::FromUTF8( ec.message() );
242bool CommitOdbDirectory(
const wxString& aSource,
const wxString& aTarget,
243 const wxString& aBoardFile,
const wxString& aJobFile,
244 REPORTER* aReporter, wxString& aError )
246 namespace fs = std::filesystem;
248 fs::path target =
ToFsPath( aTarget );
251 if( !canReplaceOdbDirectory( aTarget, aBoardFile, aJobFile, aError ) )
254 if( !fs::exists( target, ec ) )
258 aError = wxString::FromUTF8( ec.message() );
262 fs::rename(
source, target, ec );
265 aError = wxString::FromUTF8( ec.message() );
270 wxString backup = wxFileName::CreateTempFileName( aTarget + wxS(
".kicad-backup-" ) );
272 if( backup.IsEmpty() || !wxRemoveFile( backup ) )
274 aError =
_(
"Cannot create temporary backup path" );
278 fs::path backupPath =
ToFsPath( backup );
279 fs::rename( target, backupPath, ec );
283 aError = wxString::FromUTF8( ec.message() );
287 fs::rename(
source, target, ec );
291 aError = wxString::FromUTF8( ec.message() );
292 std::error_code restoreError;
293 fs::rename( backupPath, target, restoreError );
296 aError += wxS(
"\n" ) + wxString::FromUTF8( restoreError.message() );
301 fs::remove_all( backupPath, ec );
303 if( ec && aReporter )
305 aReporter->
Report( wxString::Format(
_(
"Cannot remove previous ODB++ directory '%s': %s" ),
317 const std::function<wxString(
const wxString& )>& aLocaleFold )
319 std::set<wxString> localeFolded;
320 std::set<wxString> asciiFolded;
322 for(
const wxString& token : aTokens )
324 wxString ascii = token;
326 for(
size_t ii = 0; ii < ascii.size(); ++ii )
328 wxUniChar character = ascii[ii];
330 if( character >=
'A' && character <=
'Z' )
331 ascii[ii] =
static_cast<wxChar
>( character.GetValue() -
'A' +
'a' );
334 bool newLocale = localeFolded.insert( aLocaleFold( token ) ).second;
335 bool newAscii = asciiFolded.insert( ascii ).second;
337 if( !newLocale || !newAscii )
348 wxString outputPath,
const wxString& aVariant,
349 const wxString& aProductName )
362 if( outputPath.IsEmpty() )
363 outputPath = wxFileName( aJob.
m_filename ).GetPath();
367 if( !compressed && !outputPath.IsEmpty() )
375 wxFileName outputFn( outputPath );
379 if( outputFn.GetPath().IsEmpty() && outputFn.HasName() )
380 outputFn.MakeAbsolute();
386 msg.Printf(
_(
"Cannot create output directory '%s'." ), outputFn.GetFullPath() );
394 if( outputFn.IsDir() && !outputFn.IsDirWritable() )
396 msg.Printf(
_(
"Insufficient permissions to folder '%s'." ), outputFn.GetPath() );
406 bool writable = outputFn.FileExists() ? outputFn.IsFileWritable() : outputFn.IsDirWritable();
410 msg.Printf(
_(
"Insufficient permissions to save file '%s'." ), outputFn.GetFullPath() );
418 if( outputFn.Exists() && aParentFrame )
420 msg = wxString::Format(
_(
"Output file '%s' already exists. Do you want to overwrite it?" ),
421 outputFn.GetFullPath() );
422 KIDIALOG confirm( aParentFrame, msg,
_(
"Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING );
423 confirm.SetOKLabel(
_(
"Overwrite" ) );
431 wxString replaceError;
433 if( !canReplaceOdbDirectory( outputFn.GetFullPath(), aBoard->
GetFileName(),
438 aReporter->
Report( wxString::Format(
_(
"Cannot use ODB++ directory '%s'.\n%s" ),
445 wxDir existing( outputFn.GetFullPath() );
447 if( existing.IsOpened() && ( existing.HasFiles() || existing.HasSubDirs() ) && aParentFrame )
449 msg = wxString::Format(
_(
"Output directory '%s' already exists and is not empty. "
450 "Do you want to overwrite it?" ),
451 outputFn.GetFullPath() );
452 KIDIALOG confirm( aParentFrame, msg,
_(
"Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING );
453 confirm.SetOKLabel(
_(
"Overwrite" ) );
461 wxString prefix = compressed ? wxString( wxS(
"kicad-odb" ) )
462 : outputFn.GetFullPath() + wxS(
".kicad-temp-" );
472 wxString treePath = temporary.
Path();
474 wxString product = aProductName.IsEmpty() ? odbProductBase( aJob, aBoard ) : aProductName;
475 bool named = !aProductName.IsEmpty() || !aJob.
m_productName.IsEmpty();
477 ODB_EXPORT_OPTIONS options = odbJobOptions( aJob, aVariant, named ? product : wxString() );
479 auto saveFile = [&]() ->
bool
486 plugin.
Export( treePath, *aBoard, options );
493 msg = wxString::Format(
_(
"Error generating ODB++ files '%s'.\n%s" ), treePath, error.
What() );
502 auto future = pool.submit_task( saveFile );
503 std::future_status status = future.wait_for( std::chrono::milliseconds( 250 ) );
505 while( status != std::future_status::ready )
507 if( aProgressReporter )
510 status = future.wait_for( std::chrono::milliseconds( 250 ) );
518 catch(
const std::exception& error )
522 aReporter->
Report( wxString::Format(
_(
"Exception in ODB++ generation: %s" ), error.what() ),
531 if( aProgressReporter )
532 aProgressReporter->
AdvancePhase(
_(
"Compressing output" ) );
536 wxString archiveError;
542 aReporter->
Report( wxString::Format(
_(
"Cannot write ODB++ archive '%s'.\n%s" ),
543 outputFn.GetFullPath(), archiveError ),
552 wxString commitError;
554 if( !CommitOdbDirectory( treePath, outputFn.GetFullPath(), aBoard->
GetFileName(),
559 aReporter->
Report( wxString::Format(
_(
"Cannot replace ODB++ directory '%s'.\n%s" ),
567 if( aProgressReporter )
571 result.m_outputs.push_back( outputFn.GetFullPath() );
583 wxString(), wxString(), wxString() );
608 && variants.size() > 1;
609 std::set<wxString> productNames;
610 std::vector<wxString> separateProducts;
611 std::vector<wxString> separateTokens;
613 wxString productBase = odbProductBase( aJob, aBoard );
615 if( aReporter && !legalNames.
m_renamed.empty() )
620 renamed += wxString::Format( wxS(
" '%s'" ),
name );
622 aReporter->
Report( wxString::Format(
_(
"ODB++ variant names adjusted after conversion:%s" ), renamed ),
627 aReporter->
Report(
_(
"ODB++ variant list exceeds 1000 characters; list attributes omitted." ),
630 for( wxString& variant : variants )
635 if( !variant.IsEmpty() && !aBoard->
HasVariant( variant ) )
638 aReporter->
Report( wxString::Format(
_(
"Unknown board variant '%s'." ), variant ),
646 if( variant.CmpNoCase( boardVariant ) == 0 )
648 variant = boardVariant;
657 if( token.IsEmpty() )
658 token = wxS(
"default" );
660 separateTokens.push_back( token );
662 wxString legal = variant.IsEmpty() ? wxString( wxS(
"default" ) )
664 wxString suffix = legal.Left( 62 );
665 auto productFor = [&](
const wxString& aSuffix )
667 return productBase.Left( 63 - aSuffix.length() ) + wxS(
"_" ) + aSuffix;
669 wxString product = productFor( suffix );
672 while( !productNames.insert( product ).second )
674 wxString number = wxString::Format( wxS(
"_%d" ),
duplicate++ );
675 suffix = legal.Left( 62 - number.length() ) + number;
676 product = productFor( suffix );
679 separateProducts.push_back( product );
685 [](
const wxString& aToken )
687 return aToken.Lower();
702 path, variants.front(), wxString() );
705 if( rawPath.IsEmpty() )
706 rawPath = wxS(
"odb" );
710 const wxString& variant = variants[
index];
711 const wxString& fileToken = separateTokens[
index];
713 wxString
path = rawPath;
715 if(
path.Contains( wxS(
"${VARIANT}" ) ) )
721 while( !
path.IsEmpty() && wxFileName::IsPathSeparator(
path.Last() ) )
724 wxFileName outputFn(
path );
725 outputFn.SetName( outputFn.GetName() + wxS(
"-" ) + fileToken );
726 path = outputFn.GetFullPath();
731 aReporter,
path, variant, separateProducts[
index] );
732 result.m_outputs.insert(
result.m_outputs.end(), one.m_outputs.begin(), one.m_outputs.end() );
744 bool aRemoveAutomaticSuffix )
746 if( aPath.IsEmpty() )
749 const wxString suffix = wxS(
"-${VARIANT}" );
753 wxString component = aPath;
755 while( !component.IsEmpty() && wxFileName::IsPathSeparator( component.Last() ) )
756 component.RemoveLast();
758 wxString separators = aPath.Mid( component.length() );
760 if( aRemoveAutomaticSuffix && component.EndsWith( suffix ) )
761 component.RemoveLast( suffix.length() );
763 if( aSeparate && !component.EndsWith( suffix ) )
766 return component + ( separators.IsEmpty() ? wxString( wxFileName::GetPathSeparator() ) : separators );
769 int separator = std::max( aPath.Find(
'/',
true ), aPath.Find(
'\\',
true ) );
770 int dot = aPath.Find(
'.',
true );
771 size_t stemEnd = dot > separator ?
static_cast<size_t>( dot ) : aPath.length();
772 wxString stem = aPath.Left( stemEnd );
774 if( aRemoveAutomaticSuffix && stem.EndsWith( suffix ) )
775 stem.RemoveLast( suffix.length() );
777 if( aSeparate && !stem.EndsWith( suffix ) )
780 return stem + aPath.Mid( stemEnd );
788 if( aMode == FAB::MODE::USERDEF )
813 auto reference = std::find_if( aRows.begin(), aRows.end(),
816 return aCandidate.m_matrix.m_id == aReference;
819 return reference == aRows.end() ? aReference : reference->m_matrix.m_name;
834 std::vector<ODB_MATRIX_PREVIEW_ROW> preview;
845 row.
m_matrix.
m_id = wxString::Format(
"%u", layer.m_uid );
853 if( layer.m_addType )
865 if( layer.m_cuBottom )
872 preview.push_back( std::move( row ) );
Information pertinent to a Pcbnew printed circuit board.
const std::vector< wxString > & GetVariantNames() const
bool HasVariant(const wxString &aVariantName) const
const wxString & GetFileName() const
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
wxString GetCurrentVariant() const
PROJECT * GetProject() const
SECTION_SET & Set(SECTION aSection, bool aOn=true)
virtual void SetReporter(REPORTER *aReporter)
Set an optional reporter for warnings/errors.
virtual void SetProgressReporter(PROGRESS_REPORTER *aReporter)
Set an optional progress reporter.
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()
std::optional< wxString > m_sections
Section key that replaces the optional sections of the data set Unset lets the data set select them a...
std::vector< wxString > m_variantNames
VARIANT_PACKAGING m_variantPackaging
ODB_COMPRESSION m_compressionMode
std::vector< ODB_LAYER_OVERRIDE > m_layerOverrides
bool SupportsDataSet(DATA_SET aDataSet) const override
wxString ResolveOutputPath(const wxString &aPath, bool aPathIsDirectory, PROJECT *aProject) const
wxString GetWorkingOutputPath() const
Returns the working output path for the job, if one has been set.
bool GetOutputPathIsDirectory() const
wxString GetConfiguredOutputPath() const
Returns the configured output path for the job.
Helper class to create more flexible dialogs, including 'do not show again' checkbox handling.
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
const std::vector< MATRIX_LAYER > & GetMatrixLayers() const
virtual void InitEntityData() override
static bool EnsurePathExists(const wxString &aPath, bool aPathToFile=false)
Attempts to create a given path if it does not exist.
The main frame for Pcbnew.
void ConfigureExport(BOARD &aBoard, const ODB_EXPORT_OPTIONS &aOptions)
void Export(const wxString &aFileName, BOARD &aBoard, const ODB_EXPORT_OPTIONS &aOptions)
Write the product model tree at aFileName Throw IO_ERROR when the write fails.
A progress reporter interface for use in multi-threaded environments.
virtual bool KeepRefreshing(bool aWait=false)=0
Update the UI (if any).
virtual void AdvancePhase()=0
Use the next available virtual zone of the dialog progress bar.
virtual void SetCurrentProgress(double aProgress)=0
Set the progress value to aProgress (0..1).
A pure virtual class used to derive REPORTER objects from.
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
A temporary file or directory that is removed when the object goes out of scope.
bool MakeDirectory(const wxString &aPrefix)
Create an empty directory with a unique name after aPrefix.
const wxString & Path() const
static void ResolvePossibleSymlinks(wxFileName &aFilename)
This file is part of the common library.
static bool empty(const wxTextEntryBase *aCtrl)
wxString FromFsPath(const std::filesystem::path &aPath)
bool WriteDirectoryArchive(const wxString &aSourceDir, wxOutputStream &aOut, ARCHIVE_FORMAT aFormat, const wxString &aTopDir)
Write directory entries in sorted archive path order, using the archive root when aTopDir is empty.
std::filesystem::path ToFsPath(const wxString &aPath)
Give aPath to std::filesystem without a detour through the C locale.
JOB_EXPORT_PCB_FAB::DATA_SET MODE
Columns of Table 4 The job owns the enum so that every layer shares one type.
wxString SectionKeyString(const SECTION_SET &aSet)
Give aSet as a sectionKey attribute value.
SECTION_SET RequiredSections(MODE aMode)
Schema sections that Table 4 marks Y for aMode.
bool SectionSetFromKeyString(const wxString &aKey, SECTION_SET &aResult)
Read a sectionKey attribute value Return false for an unknown character.
SECTION_SET RecommendedOptionalSections(MODE aMode)
Optional schema sections that aMode selects by default.
std::string Enum2String(T value)
wxString GenLegalEntityName(const wxString &aStr)
wxString OdbPreviewReferenceName(const wxString &aReference, const std::vector< ODB_MATRIX_PREVIEW_ROW > &aRows)
Matrix name of the row whose ID is aReference.
wxString OdbSectionKeyForSelection(FAB::MODE aMode, const FAB::SECTION_SET &aSelection)
Use an empty section key when Customize matches the data set default.
wxString UpdateOdbVariantOutputPath(const wxString &aPath, bool aDirectory, bool aSeparate, bool aRemoveAutomaticSuffix)
Add or remove the automatic variant suffix on the file stem or final directory.
std::vector< ODB_MATRIX_PREVIEW_ROW > PreviewOdbMatrix(BOARD *aBoard, const JOB_EXPORT_PCB_ODB &aJob)
Build export matrix rows without layer overrides or writing files.
bool OdbVariantFileTokensCollide(const std::vector< wxString > &aTokens, const std::function< wxString(const wxString &)> &aLocaleFold)
Check whether variant output tokens could name the same file.
static ODB_EXPORT_RESULT generateOneODBPackage(const JOB_EXPORT_PCB_ODB &aJob, BOARD *aBoard, PCB_EDIT_FRAME *aParentFrame, PROGRESS_REPORTER *aProgressReporter, REPORTER *aReporter, wxString outputPath, const wxString &aVariant, const wxString &aProductName)
FAB::SECTION_SET OdbDefaultSections(FAB::MODE aMode)
Sections a data set exports when no section key is given.
ODB_EXPORT_RESULT GenerateODBPPFiles(const JOB_EXPORT_PCB_ODB &aJob, BOARD *aBoard, PCB_EDIT_FRAME *aParentFrame, PROGRESS_REPORTER *aProgressReporter, REPORTER *aReporter)
Generate the ODB++ tree or archive and return the write status and the paths written.
wxString GetDefaultVariantName()
wxString ExpandVariantOutputPath(const wxString &aPath, const wxString &aVariantToken)
wxString GetVariantFileToken(const wxString &aVariantName)
wxString LegalName(const wxString &aName) const
std::vector< wxString > m_renamed
static VARIANT_NAMES Build(const std::vector< wxString > &aNames)
What a caller asks of one ODB++ export.
std::optional< wxString > m_variant
Unset exports the current variant of the board.
JOB_EXPORT_PCB_ODB::ORIGIN m_origin
std::optional< FAB::SECTION_SET > m_sections
Unset exports every section.
wxString m_productModelName
std::vector< ODB_LAYER_OVERRIDE > m_layerOverrides
One matrix row with its KiCad layer for the dialog's layer grid.
PCB_LAYER_ID m_boardLayer
wxString m_dielectricType
wxString result
Test unit parsing edge cases and error handling.
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
BS::priority_thread_pool thread_pool