KiCad PCB EDA Suite
Loading...
Searching...
No Matches
odb_export_job.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include "odb_export_job.h"
21#include "odb_entity.h"
22#include "odb_util.h"
23#include "pcb_io_odbpp.h"
24
25#include <thread_pool.h>
26#include <gestfich.h>
27
28#include <algorithm>
29#include <array>
30#include <filesystem>
31#include <set>
32
33#include <wx/dir.h>
34
35#include <board.h>
36#include <reporter.h>
37#include <confirm.h>
38#include <kidialog.h>
39#include <paths.h>
40#include <pcb_edit_frame.h>
41#include <progress_reporter.h>
42#include <project.h>
44#include <locale_io.h>
45#include <string_utils.h>
46
47
48namespace
49{
50bool containsBoardFile( const std::filesystem::path& aTarget, const wxString& aBoardFile )
51{
52 namespace fs = std::filesystem;
53
54 if( aBoardFile.IsEmpty() )
55 return false;
56
57 fs::path file = ToFsPath( aBoardFile );
58 std::error_code ec;
59
60 if( !fs::exists( file, ec ) )
61 return bool( ec );
62
63 for( fs::path dir = file.parent_path(); !dir.empty(); dir = dir.parent_path() )
64 {
65 if( fs::equivalent( aTarget, dir, ec ) )
66 return true;
67
68 if( ec )
69 return true;
70
71 if( dir == dir.root_path() )
72 break;
73 }
74
75 return false;
76}
77
78
79wxString odbProductBase( const JOB_EXPORT_PCB_ODB& aJob, const BOARD* aBoard )
80{
81 if( !aJob.m_productName.IsEmpty() )
82 {
83 wxString requested = ODB::GenLegalEntityName( aJob.m_productName );
84
85 if( !requested.IsEmpty() )
86 return requested;
87 }
88
89 wxString boardName = wxFileName( aBoard->GetFileName() ).GetName();
90
91 if( boardName.IsEmpty() )
92 boardName = wxFileName( aJob.m_filename ).GetName();
93
94 wxString product = ODB::GenLegalEntityName( boardName );
95
96 if( product.IsEmpty() )
97 product = wxS( "odb" );
98
99 return product;
100}
101
102
104ODB_EXPORT_OPTIONS odbJobOptions( const JOB_EXPORT_PCB_ODB& aJob, const wxString& aVariant,
105 const wxString& aProductModelName )
106{
107 ODB_EXPORT_OPTIONS options;
109 options.m_precision = aJob.m_precision;
110 options.m_variant = aVariant;
111 options.m_mpnField = aJob.m_colMfgPn;
112 options.m_productModelName = aProductModelName;
113 options.m_origin = aJob.m_origin;
115 options.m_boardMetadata = aJob.m_boardMetadata;
116 options.m_layerOverrides = aJob.m_layerOverrides;
117
118 // USERDEF with no section key keeps the unfiltered matrix
120 options.m_sections = OdbDefaultSections( aJob.m_dataSet );
121
122 FAB::SECTION_SET sections;
123
124 if( aJob.m_sections && !aJob.m_sections->IsEmpty() && FAB::SectionSetFromKeyString( *aJob.m_sections, sections ) )
125 options.m_sections = sections;
126
127 return options;
128}
129
130
131bool canReplaceOdbDirectory( const wxString& aTarget, const wxString& aBoardFile,
132 const wxString& aJobFile, wxString& aError )
133{
134 namespace fs = std::filesystem;
135 fs::path target = ToFsPath( aTarget );
136 std::error_code ec;
137
138 if( !fs::exists( target, ec ) )
139 {
140 if( ec )
141 aError = wxString::FromUTF8( ec.message() );
142
143 return !ec;
144 }
145
146 if( !fs::is_directory( target, ec ) )
147 {
148 aError = ec ? wxString::FromUTF8( ec.message() ) : _( "Output path is not a directory" );
149 return false;
150 }
151
152 bool empty = fs::is_empty( target, ec );
153
154 if( ec )
155 {
156 aError = wxString::FromUTF8( ec.message() );
157 return false;
158 }
159
160 if( empty )
161 return true;
162
163 if( !fs::is_regular_file( target / "matrix" / "matrix", ec ) )
164 {
165 aError = _( "Output directory is not an ODB++ product" );
166
167 if( ec )
168 aError += wxS( "\n" ) + wxString::FromUTF8( ec.message() );
169
170 return false;
171 }
172
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" )
176 };
177
178 fs::directory_iterator child( target, ec );
179 fs::directory_iterator end;
180
181 while( !ec && child != end )
182 {
183 fs::path name = child->path().filename();
184
185 if( std::find( allowed.begin(), allowed.end(), name ) == allowed.end() )
186 {
187 aError = wxString::Format( _( "Output directory contains unrelated entry '%s'" ), FromFsPath( name ) );
188 return false;
189 }
190
191 child.increment( ec );
192 }
193
194 if( ec )
195 {
196 aError = wxString::FromUTF8( ec.message() );
197 return false;
198 }
199
200 if( containsBoardFile( target, aBoardFile ) || containsBoardFile( target, aJobFile ) )
201 {
202 aError = _( "Output directory contains the source board" );
203 return false;
204 }
205
206 fs::recursive_directory_iterator nested( target, ec );
207 fs::recursive_directory_iterator nestedEnd;
208
209 while( !ec && nested != nestedEnd )
210 {
211 bool regular = nested->is_regular_file( ec );
212
213 if( ec )
214 {
215 aError = wxString::FromUTF8( ec.message() );
216 return false;
217 }
218
219 if( regular )
220 {
221 wxString extension = FromFsPath( nested->path().extension() ).Lower();
222
223 if( extension.StartsWith( wxS( ".kicad_" ) ) || extension == wxS( ".pro" )
224 || extension == wxS( ".sch" ) || extension == wxS( ".pcb" ) )
225 {
226 aError = wxString::Format( _( "Output directory contains project file '%s'" ),
227 FromFsPath( nested->path().filename() ) );
228 return false;
229 }
230 }
231
232 nested.increment( ec );
233 }
234
235 if( ec )
236 aError = wxString::FromUTF8( ec.message() );
237
238 return !ec;
239}
240
241
242bool CommitOdbDirectory( const wxString& aSource, const wxString& aTarget,
243 const wxString& aBoardFile, const wxString& aJobFile,
244 REPORTER* aReporter, wxString& aError )
245{
246 namespace fs = std::filesystem;
247 fs::path source = ToFsPath( aSource );
248 fs::path target = ToFsPath( aTarget );
249 std::error_code ec;
250
251 if( !canReplaceOdbDirectory( aTarget, aBoardFile, aJobFile, aError ) )
252 return false;
253
254 if( !fs::exists( target, ec ) )
255 {
256 if( ec )
257 {
258 aError = wxString::FromUTF8( ec.message() );
259 return false;
260 }
261
262 fs::rename( source, target, ec );
263
264 if( ec )
265 aError = wxString::FromUTF8( ec.message() );
266
267 return !ec;
268 }
269
270 wxString backup = wxFileName::CreateTempFileName( aTarget + wxS( ".kicad-backup-" ) );
271
272 if( backup.IsEmpty() || !wxRemoveFile( backup ) )
273 {
274 aError = _( "Cannot create temporary backup path" );
275 return false;
276 }
277
278 fs::path backupPath = ToFsPath( backup );
279 fs::rename( target, backupPath, ec );
280
281 if( ec )
282 {
283 aError = wxString::FromUTF8( ec.message() );
284 return false;
285 }
286
287 fs::rename( source, target, ec );
288
289 if( ec )
290 {
291 aError = wxString::FromUTF8( ec.message() );
292 std::error_code restoreError;
293 fs::rename( backupPath, target, restoreError );
294
295 if( restoreError )
296 aError += wxS( "\n" ) + wxString::FromUTF8( restoreError.message() );
297
298 return false;
299 }
300
301 fs::remove_all( backupPath, ec );
302
303 if( ec && aReporter )
304 {
305 aReporter->Report( wxString::Format( _( "Cannot remove previous ODB++ directory '%s': %s" ),
306 backup, wxString::FromUTF8( ec.message() ) ), RPT_SEVERITY_WARNING );
307 }
308
309 return true;
310}
311
312
313} // namespace
314
315
316bool OdbVariantFileTokensCollide( const std::vector<wxString>& aTokens,
317 const std::function<wxString( const wxString& )>& aLocaleFold )
318{
319 std::set<wxString> localeFolded;
320 std::set<wxString> asciiFolded;
321
322 for( const wxString& token : aTokens )
323 {
324 wxString ascii = token;
325
326 for( size_t ii = 0; ii < ascii.size(); ++ii )
327 {
328 wxUniChar character = ascii[ii];
329
330 if( character >= 'A' && character <= 'Z' )
331 ascii[ii] = static_cast<wxChar>( character.GetValue() - 'A' + 'a' );
332 }
333
334 bool newLocale = localeFolded.insert( aLocaleFold( token ) ).second;
335 bool newAscii = asciiFolded.insert( ascii ).second;
336
337 if( !newLocale || !newAscii )
338 return true;
339 }
340
341 return false;
342}
343
344
346 PCB_EDIT_FRAME* aParentFrame,
347 PROGRESS_REPORTER* aProgressReporter, REPORTER* aReporter,
348 wxString outputPath, const wxString& aVariant,
349 const wxString& aProductName )
350{
351 LOCALE_IO toggle;
353
354 if( !aBoard )
355 {
356 if( aReporter )
357 aReporter->Report( _( "No board for ODB++ export." ), RPT_SEVERITY_ERROR );
358
359 return result;
360 }
361
362 if( outputPath.IsEmpty() )
363 outputPath = wxFileName( aJob.m_filename ).GetPath();
364
366
367 if( !compressed && !outputPath.IsEmpty() )
368 {
369 std::filesystem::path directory = ToFsPath( outputPath );
370
371 if( directory.filename().empty() && directory != directory.root_path() )
372 outputPath = FromFsPath( directory.parent_path() );
373 }
374
375 wxFileName outputFn( outputPath );
376 // Write through symlinks, don't replace them
378
379 if( outputFn.GetPath().IsEmpty() && outputFn.HasName() )
380 outputFn.MakeAbsolute();
381
382 wxString msg;
383
384 if( !PATHS::EnsurePathExists( outputFn.GetFullPath(), true ) )
385 {
386 msg.Printf( _( "Cannot create output directory '%s'." ), outputFn.GetFullPath() );
387
388 if( aReporter )
389 aReporter->Report( msg, RPT_SEVERITY_ERROR );
390
391 return result;
392 }
393
394 if( outputFn.IsDir() && !outputFn.IsDirWritable() )
395 {
396 msg.Printf( _( "Insufficient permissions to folder '%s'." ), outputFn.GetPath() );
397
398 if( aReporter )
399 aReporter->Report( msg, RPT_SEVERITY_ERROR );
400
401 return result;
402 }
403
404 if( compressed )
405 {
406 bool writable = outputFn.FileExists() ? outputFn.IsFileWritable() : outputFn.IsDirWritable();
407
408 if( !writable )
409 {
410 msg.Printf( _( "Insufficient permissions to save file '%s'." ), outputFn.GetFullPath() );
411
412 if( aReporter )
413 aReporter->Report( msg, RPT_SEVERITY_ERROR );
414
415 return result;
416 }
417
418 if( outputFn.Exists() && aParentFrame )
419 {
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" ) );
424
425 if( confirm.ShowModal() != wxID_OK )
426 return result;
427 }
428 }
429 else
430 {
431 wxString replaceError;
432
433 if( !canReplaceOdbDirectory( outputFn.GetFullPath(), aBoard->GetFileName(),
434 aJob.m_filename, replaceError ) )
435 {
436 if( aReporter )
437 {
438 aReporter->Report( wxString::Format( _( "Cannot use ODB++ directory '%s'.\n%s" ),
439 outputFn.GetFullPath(), replaceError ), RPT_SEVERITY_ERROR );
440 }
441
442 return result;
443 }
444
445 wxDir existing( outputFn.GetFullPath() );
446
447 if( existing.IsOpened() && ( existing.HasFiles() || existing.HasSubDirs() ) && aParentFrame )
448 {
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" ) );
454
455 if( confirm.ShowModal() != wxID_OK )
456 return result;
457 }
458 }
459
460 SCOPED_TEMP_PATH temporary;
461 wxString prefix = compressed ? wxString( wxS( "kicad-odb" ) )
462 : outputFn.GetFullPath() + wxS( ".kicad-temp-" );
463
464 if( !temporary.MakeDirectory( prefix ) )
465 {
466 if( aReporter )
467 aReporter->Report( _( "Cannot create temporary output directory." ), RPT_SEVERITY_ERROR );
468
469 return result;
470 }
471
472 wxString treePath = temporary.Path();
473
474 wxString product = aProductName.IsEmpty() ? odbProductBase( aJob, aBoard ) : aProductName;
475 bool named = !aProductName.IsEmpty() || !aJob.m_productName.IsEmpty();
476
477 ODB_EXPORT_OPTIONS options = odbJobOptions( aJob, aVariant, named ? product : wxString() );
478
479 auto saveFile = [&]() -> bool
480 {
481 try
482 {
483 PCB_IO_ODBPP plugin;
484 plugin.SetReporter( aReporter );
485 plugin.SetProgressReporter( aProgressReporter );
486 plugin.Export( treePath, *aBoard, options );
487 return true;
488 }
489 catch( const IO_ERROR& error )
490 {
491 if( aReporter )
492 {
493 msg = wxString::Format( _( "Error generating ODB++ files '%s'.\n%s" ), treePath, error.What() );
494 aReporter->Report( msg, RPT_SEVERITY_ERROR );
495 }
496
497 return false;
498 }
499 };
500
502 auto future = pool.submit_task( saveFile );
503 std::future_status status = future.wait_for( std::chrono::milliseconds( 250 ) );
504
505 while( status != std::future_status::ready )
506 {
507 if( aProgressReporter )
508 aProgressReporter->KeepRefreshing();
509
510 status = future.wait_for( std::chrono::milliseconds( 250 ) );
511 }
512
513 try
514 {
515 if( !future.get() )
516 return result;
517 }
518 catch( const std::exception& error )
519 {
520 if( aReporter )
521 {
522 aReporter->Report( wxString::Format( _( "Exception in ODB++ generation: %s" ), error.what() ),
524 }
525
526 return result;
527 }
528
529 if( compressed )
530 {
531 if( aProgressReporter )
532 aProgressReporter->AdvancePhase( _( "Compressing output" ) );
533
536 wxString archiveError;
537
538 if( !WriteDirectoryArchive( treePath, outputFn.GetFullPath(), format, product, &archiveError ) )
539 {
540 if( aReporter )
541 {
542 aReporter->Report( wxString::Format( _( "Cannot write ODB++ archive '%s'.\n%s" ),
543 outputFn.GetFullPath(), archiveError ),
545 }
546
547 return result;
548 }
549 }
550 else
551 {
552 wxString commitError;
553
554 if( !CommitOdbDirectory( treePath, outputFn.GetFullPath(), aBoard->GetFileName(),
555 aJob.m_filename, aReporter, commitError ) )
556 {
557 if( aReporter )
558 {
559 aReporter->Report( wxString::Format( _( "Cannot replace ODB++ directory '%s'.\n%s" ),
560 outputFn.GetFullPath(), commitError ), RPT_SEVERITY_ERROR );
561 }
562
563 return result;
564 }
565 }
566
567 if( aProgressReporter )
568 aProgressReporter->SetCurrentProgress( 1 );
569
570 result.m_ok = true;
571 result.m_outputs.push_back( outputFn.GetFullPath() );
572 return result;
573}
574
575
577 PROGRESS_REPORTER* aProgressReporter, REPORTER* aReporter )
578{
580
581 if( !aBoard )
582 return generateOneODBPackage( aJob, aBoard, aParentFrame, aProgressReporter, aReporter,
583 wxString(), wxString(), wxString() );
584
585 if( !aJob.SupportsDataSet( aJob.m_dataSet ) )
586 {
587 if( aReporter )
588 aReporter->Report( _( "Unsupported ODB++ data set." ), RPT_SEVERITY_ERROR );
589
590 return result;
591 }
592
593 FAB::SECTION_SET sections;
594
595 if( !FAB::SectionSetFromKeyString( aJob.m_sections.value_or( wxString() ), sections ) )
596 {
597 if( aReporter )
598 aReporter->Report( _( "Unknown ODB++ section key." ), RPT_SEVERITY_ERROR );
599
600 return result;
601 }
602
603 std::vector<wxString> variants = aJob.m_variantNames.empty()
604 ? std::vector<wxString>{ aBoard->GetCurrentVariant() } : aJob.m_variantNames;
605 wxString rawPath = aJob.GetWorkingOutputPath().IsEmpty() ? aJob.GetConfiguredOutputPath()
606 : aJob.GetWorkingOutputPath();
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 );
614
615 if( aReporter && !legalNames.m_renamed.empty() )
616 {
617 wxString renamed;
618
619 for( const wxString& name : legalNames.m_renamed )
620 renamed += wxString::Format( wxS( " '%s'" ), name );
621
622 aReporter->Report( wxString::Format( _( "ODB++ variant names adjusted after conversion:%s" ), renamed ),
624 }
625
626 if( aReporter && !legalNames.m_listsFit )
627 aReporter->Report( _( "ODB++ variant list exceeds 1000 characters; list attributes omitted." ),
629
630 for( wxString& variant : variants )
631 {
632 if( variant.CmpNoCase( GetDefaultVariantName() ) == 0 )
633 variant.clear();
634
635 if( !variant.IsEmpty() && !aBoard->HasVariant( variant ) )
636 {
637 if( aReporter )
638 aReporter->Report( wxString::Format( _( "Unknown board variant '%s'." ), variant ),
640
641 return result;
642 }
643
644 for( const wxString& boardVariant : aBoard->GetVariantNames() )
645 {
646 if( variant.CmpNoCase( boardVariant ) == 0 )
647 {
648 variant = boardVariant;
649 break;
650 }
651 }
652
653 if( separate )
654 {
655 wxString token = GetVariantFileToken( variant );
656
657 if( token.IsEmpty() )
658 token = wxS( "default" );
659
660 separateTokens.push_back( token );
661
662 wxString legal = variant.IsEmpty() ? wxString( wxS( "default" ) )
663 : legalNames.LegalName( variant );
664 wxString suffix = legal.Left( 62 );
665 auto productFor = [&]( const wxString& aSuffix )
666 {
667 return productBase.Left( 63 - aSuffix.length() ) + wxS( "_" ) + aSuffix;
668 };
669 wxString product = productFor( suffix );
670 int duplicate = 2;
671
672 while( !productNames.insert( product ).second )
673 {
674 wxString number = wxString::Format( wxS( "_%d" ), duplicate++ );
675 suffix = legal.Left( 62 - number.length() ) + number;
676 product = productFor( suffix );
677 }
678
679 separateProducts.push_back( product );
680 }
681 }
682
683 if( separate
684 && OdbVariantFileTokensCollide( separateTokens,
685 []( const wxString& aToken )
686 {
687 return aToken.Lower();
688 } ) )
689 {
690 if( aReporter )
691 aReporter->Report( _( "ODB++ variant output names collide after conversion." ), RPT_SEVERITY_ERROR );
692
693 return result;
694 }
695
696 if( !separate )
697 {
698 wxString path = aJob.ResolveOutputPath( ExpandVariantOutputPath( rawPath,
699 GetVariantFileToken( variants.front() ) ),
700 aJob.GetOutputPathIsDirectory(), aBoard->GetProject() );
701 return generateOneODBPackage( aJob, aBoard, aParentFrame, aProgressReporter, aReporter,
702 path, variants.front(), wxString() );
703 }
704
705 if( rawPath.IsEmpty() )
706 rawPath = wxS( "odb" );
707
708 for( size_t index = 0; index < variants.size(); ++index )
709 {
710 const wxString& variant = variants[index];
711 const wxString& fileToken = separateTokens[index];
712
713 wxString path = rawPath;
714
715 if( path.Contains( wxS( "${VARIANT}" ) ) )
716 {
717 path = ExpandVariantOutputPath( path, fileToken );
718 }
719 else
720 {
721 while( !path.IsEmpty() && wxFileName::IsPathSeparator( path.Last() ) )
722 path.RemoveLast();
723
724 wxFileName outputFn( path );
725 outputFn.SetName( outputFn.GetName() + wxS( "-" ) + fileToken );
726 path = outputFn.GetFullPath();
727 }
728
730 ODB_EXPORT_RESULT one = generateOneODBPackage( aJob, aBoard, aParentFrame, aProgressReporter,
731 aReporter, path, variant, separateProducts[index] );
732 result.m_outputs.insert( result.m_outputs.end(), one.m_outputs.begin(), one.m_outputs.end() );
733
734 if( !one.m_ok )
735 return result;
736 }
737
738 result.m_ok = true;
739 return result;
740}
741
742
743wxString UpdateOdbVariantOutputPath( const wxString& aPath, bool aDirectory, bool aSeparate,
744 bool aRemoveAutomaticSuffix )
745{
746 if( aPath.IsEmpty() )
747 return aPath;
748
749 const wxString suffix = wxS( "-${VARIANT}" );
750
751 if( aDirectory )
752 {
753 wxString component = aPath;
754
755 while( !component.IsEmpty() && wxFileName::IsPathSeparator( component.Last() ) )
756 component.RemoveLast();
757
758 wxString separators = aPath.Mid( component.length() );
759
760 if( aRemoveAutomaticSuffix && component.EndsWith( suffix ) )
761 component.RemoveLast( suffix.length() );
762
763 if( aSeparate && !component.EndsWith( suffix ) )
764 component += suffix;
765
766 return component + ( separators.IsEmpty() ? wxString( wxFileName::GetPathSeparator() ) : separators );
767 }
768
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 );
773
774 if( aRemoveAutomaticSuffix && stem.EndsWith( suffix ) )
775 stem.RemoveLast( suffix.length() );
776
777 if( aSeparate && !stem.EndsWith( suffix ) )
778 stem += suffix;
779
780 return stem + aPath.Mid( stemEnd );
781}
782
783
785{
786 FAB::SECTION_SET defaults;
787
788 if( aMode == FAB::MODE::USERDEF )
789 {
790 defaults.set();
791 defaults.Set( FAB::SECTION::DFX, false );
792 }
793 else
794 {
795 defaults = FAB::RequiredSections( aMode ) | FAB::RecommendedOptionalSections( aMode );
796 }
797
798 return defaults;
799}
800
801
802wxString OdbSectionKeyForSelection( FAB::MODE aMode, const FAB::SECTION_SET& aSelection )
803{
804 if( aSelection == OdbDefaultSections( aMode ) )
805 return wxString();
806
807 return FAB::SectionKeyString( aSelection );
808}
809
810
811wxString OdbPreviewReferenceName( const wxString& aReference, const std::vector<ODB_MATRIX_PREVIEW_ROW>& aRows )
812{
813 auto reference = std::find_if( aRows.begin(), aRows.end(),
814 [&]( const ODB_MATRIX_PREVIEW_ROW& aCandidate )
815 {
816 return aCandidate.m_matrix.m_id == aReference;
817 } );
818
819 return reference == aRows.end() ? aReference : reference->m_matrix.m_name;
820}
821
822
823std::vector<ODB_MATRIX_PREVIEW_ROW> PreviewOdbMatrix( BOARD* aBoard, const JOB_EXPORT_PCB_ODB& aJob )
824{
825 if( !aBoard )
826 return {};
827
828 ODB_EXPORT_OPTIONS options = odbJobOptions( aJob, aBoard->GetCurrentVariant(), wxString() );
829 options.m_layerOverrides.clear();
830 PCB_IO_ODBPP plugin;
831 plugin.ConfigureExport( *aBoard, options );
832 ODB_MATRIX_ENTITY matrix( aBoard, &plugin );
833 matrix.InitEntityData();
834 std::vector<ODB_MATRIX_PREVIEW_ROW> preview;
835 preview.reserve( matrix.GetMatrixLayers().size() );
836
837 for( const ODB_MATRIX_ENTITY::MATRIX_LAYER& layer : matrix.GetMatrixLayers() )
838 {
840 row.m_matrix.m_row = static_cast<int>( layer.m_rowNumber );
841 row.m_matrix.m_name = layer.m_layerName;
842 row.m_matrix.m_type = wxString::FromUTF8( ODB::Enum2String( layer.m_type ) );
843 row.m_matrix.m_context = wxString::FromUTF8( ODB::Enum2String( layer.m_context ) );
844 row.m_matrix.m_polarity = wxString::FromUTF8( ODB::Enum2String( layer.m_polarity ) );
845 row.m_matrix.m_id = wxString::Format( "%u", layer.m_uid );
846
847 if( layer.m_span )
848 {
849 row.m_matrix.m_startName = layer.m_span->first;
850 row.m_matrix.m_endName = layer.m_span->second;
851 }
852
853 if( layer.m_addType )
854 row.m_matrix.m_addType = wxString::FromUTF8( ODB::Enum2String( *layer.m_addType ) );
855
856 if( layer.m_diType )
857 row.m_matrix.m_dielectricType = wxString::FromUTF8( ODB::Enum2String( *layer.m_diType ) );
858
859 if( layer.m_ref )
860 row.m_matrix.m_ref = wxString::Format( "%u", *layer.m_ref );
861
862 if( layer.m_cuTop )
863 row.m_matrix.m_cuTop = wxString::Format( "%u", *layer.m_cuTop );
864
865 if( layer.m_cuBottom )
866 row.m_matrix.m_cuBottom = wxString::Format( "%u", *layer.m_cuBottom );
867
868 row.m_boardLayer = layer.m_info.m_layer;
869 row.m_editable = layer.m_info.m_role == ODB_LAYER_ROLE::BOARD_LAYER && row.m_boardLayer != UNDEFINED_LAYER;
870 row.m_displayLayer = row.m_editable ? aBoard->GetLayerName( row.m_boardLayer )
871 : wxString::Format( "(%s)", row.m_matrix.m_type.Lower() );
872 preview.push_back( std::move( row ) );
873 }
874
875 return preview;
876}
int index
const char * name
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:410
const std::vector< wxString > & GetVariantNames() const
Definition board.h:532
bool HasVariant(const wxString &aVariantName) const
Definition board.cpp:3199
const wxString & GetFileName() const
Definition board.h:453
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:943
wxString GetCurrentVariant() const
Definition board.h:529
PROJECT * GetProject() const
Definition board.h:775
Set of schema sections.
SECTION_SET & Set(SECTION aSection, bool aOn=true)
virtual void SetReporter(REPORTER *aReporter)
Set an optional reporter for warnings/errors.
Definition io_base.h:89
virtual void SetProgressReporter(PROGRESS_REPORTER *aReporter)
Set an optional progress reporter.
Definition io_base.h:94
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
Definition job.cpp:100
wxString GetWorkingOutputPath() const
Returns the working output path for the job, if one has been set.
Definition job.h:246
bool GetOutputPathIsDirectory() const
Definition job.h:258
wxString GetConfiguredOutputPath() const
Returns the configured output path for the job.
Definition job.h:235
Helper class to create more flexible dialogs, including 'do not show again' checkbox handling.
Definition kidialog.h:38
int ShowModal() override
Definition kidialog.cpp:89
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:37
const std::vector< MATRIX_LAYER > & GetMatrixLayers() const
Definition odb_entity.h:127
virtual void InitEntityData() override
static bool EnsurePathExists(const wxString &aPath, bool aPathToFile=false)
Attempts to create a given path if it does not exist.
Definition paths.cpp:508
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.
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
A temporary file or directory that is removed when the object goes out of scope.
Definition gestfich.h:62
bool MakeDirectory(const wxString &aPrefix)
Create an empty directory with a unique name after aPrefix.
Definition gestfich.cpp:946
const wxString & Path() const
Definition gestfich.h:75
static void ResolvePossibleSymlinks(wxFileName &aFilename)
This file is part of the common library.
static bool empty(const wxTextEntryBase *aCtrl)
#define _(s)
wxString FromFsPath(const std::filesystem::path &aPath)
Definition gestfich.cpp:919
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.
Definition gestfich.cpp:793
std::filesystem::path ToFsPath(const wxString &aPath)
Give aPath to std::filesystem without a detour through the C locale.
Definition gestfich.cpp:909
ARCHIVE_FORMAT
Definition gestfich.h:50
std::string source
@ UNDEFINED_LAYER
Definition layer_ids.h:57
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)
Definition odb_util.h:279
wxString GenLegalEntityName(const wxString &aStr)
Definition odb_util.cpp:212
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.
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
wxString GetDefaultVariantName()
wxString ExpandVariantOutputPath(const wxString &aPath, const wxString &aVariantToken)
wxString GetVariantFileToken(const wxString &aVariantName)
wxString LegalName(const wxString &aName) const
Definition odb_util.cpp:331
std::vector< wxString > m_renamed
Definition odb_util.h:43
static VARIANT_NAMES Build(const std::vector< wxString > &aNames)
Definition odb_util.cpp:298
What a caller asks of one ODB++ export.
Definition odb_util.h:50
std::optional< wxString > m_variant
Unset exports the current variant of the board.
Definition odb_util.h:54
wxString m_mpnField
Definition odb_util.h:55
JOB_EXPORT_PCB_ODB::ORIGIN m_origin
Definition odb_util.h:57
std::optional< FAB::SECTION_SET > m_sections
Unset exports every section.
Definition odb_util.h:61
wxString m_productModelName
Definition odb_util.h:56
std::vector< ODB_LAYER_OVERRIDE > m_layerOverrides
Definition odb_util.h:62
One matrix row with its KiCad layer for the dialog's layer grid.
ODB_MATRIX_ROW m_matrix
wxString m_startName
wxString m_cuBottom
wxString m_dielectricType
wxString m_polarity
std::string path
VECTOR2I end
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
Definition thread_pool.h:27