KiCad PCB EDA Suite
Loading...
Searching...
No Matches
footprint_import_reconciler.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
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU 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
21
22#include <algorithm>
23#include <map>
24#include <memory>
25#include <set>
26#include <tuple>
27#include <utility>
28
29#include <wx/dir.h>
30#include <wx/filename.h>
31
32#include <board.h>
33#include <footprint.h>
35#include <lib_id.h>
36#include <pad.h>
37#include <project.h>
38#include <project_pcb.h>
39#include <reporter.h>
40#include <string_utils.h>
42#include <io/io_mgr.h>
43#include <pcb_io/pcb_io.h>
44#include <pcb_io/pcb_io_mgr.h>
47
48
50 const wxString& aProjectPath,
51 REPORTER& aReporter ) :
52 m_adapter( aAdapter ),
53 m_projectPath( aProjectPath ),
54 m_reporter( aReporter )
55{
56}
57
58
59namespace
60{
61std::multiset<wxString> padNumbers( const FOOTPRINT& aFp )
62{
63 std::multiset<wxString> numbers;
64
65 for( const PAD* pad : aFp.Pads() )
66 numbers.insert( pad->GetNumber() );
67
68 return numbers;
69}
70
71
72// two tools never draw a footprint identically, so equivalence is the pad set
73bool sameInterface( const FOOTPRINT& aLhs, const FOOTPRINT& aRhs )
74{
75 return padNumbers( aLhs ) == padNumbers( aRhs );
76}
77
78
79// unset and explicit default thermal settings resolve the same
80bool sameCopperOverrides( const PADSTACK& aLhs, const PADSTACK& aRhs, PCB_LAYER_ID aLayer )
81{
82 const PADSTACK::COPPER_LAYER_PROPS& lhs = aLhs.CopperLayer( aLayer );
83 const PADSTACK::COPPER_LAYER_PROPS& rhs = aRhs.CopperLayer( aLayer );
84
85 return lhs.zone_connection.value_or( ZONE_CONNECTION::INHERITED )
88 && aLhs.ThermalSpokeAngle( aLayer ) == aRhs.ThermalSpokeAngle( aLayer )
89 && lhs.thermal_gap == rhs.thermal_gap
90 && lhs.clearance == rhs.clearance
91 && ( aLhs.Shape( aLayer ) != PAD_SHAPE::CUSTOM || aLhs.AnchorShape( aLayer ) == aRhs.AnchorShape( aLayer ) );
92}
93
94
95// the parity check skips pad overrides and fab settings, yet updating from the library replaces them
96bool samePadOverrides( FOOTPRINT& aInstance, const FOOTPRINT& aDef )
97{
98 std::unique_ptr<FOOTPRINT> def( static_cast<FOOTPRINT*>( aDef.Clone() ) );
99
100 def->SetParent( aInstance.GetBoard() );
101
102 if( def->IsFlipped() != aInstance.IsFlipped() )
103 def->Flip( { 0, 0 }, FLIP_DIRECTION::TOP_BOTTOM );
104
105 std::set<PAD*, FOOTPRINT::cmp_pads> lhs( aInstance.Pads().begin(), aInstance.Pads().end() );
106 std::set<PAD*, FOOTPRINT::cmp_pads> rhs( def->Pads().begin(), def->Pads().end() );
107
108 bool same = std::equal( lhs.begin(), lhs.end(), rhs.begin(), rhs.end(),
109 []( const PAD* aLhs, const PAD* aRhs )
110 {
111 const PADSTACK& lhsStack = aLhs->Padstack();
112 const PADSTACK& rhsStack = aRhs->Padstack();
113
114 if( !( lhsStack.FrontOuterLayers() == rhsStack.FrontOuterLayers() )
115 || !( lhsStack.BackOuterLayers() == rhsStack.BackOuterLayers() )
116 || aLhs->GetCustomShapeInZoneOpt() != aRhs->GetCustomShapeInZoneOpt()
117 || !( aLhs->GetTeardropParams() == aRhs->GetTeardropParams() )
118 || aLhs->GetPadToDieDelay() != aRhs->GetPadToDieDelay()
119 || aLhs->GetSimElectricalType() != aRhs->GetSimElectricalType()
120 || aLhs->GetTopBackdrillSize() != aRhs->GetTopBackdrillSize()
121 || aLhs->GetTopBackdrillLayer() != aRhs->GetTopBackdrillLayer()
122 || aLhs->GetBottomBackdrillSize() != aRhs->GetBottomBackdrillSize()
123 || aLhs->GetBottomBackdrillLayer() != aRhs->GetBottomBackdrillLayer()
124 || !( lhsStack.FrontPostMachining() == rhsStack.FrontPostMachining() )
125 || !( lhsStack.BackPostMachining() == rhsStack.BackPostMachining() ) )
126 {
127 return false;
128 }
129
130 bool layersMatch = true;
131
132 // the stacks may group layers differently, so walk the layers of both
133 auto check =
134 [&]( PCB_LAYER_ID aLayer )
135 {
136 layersMatch &= sameCopperOverrides( lhsStack, rhsStack, aLayer );
137 };
138
139 lhsStack.ForEachUniqueLayer( check );
140 rhsStack.ForEachUniqueLayer( check );
141
142 return layersMatch;
143 } );
144
145 // a parentless temporary must not touch the board timestamp when it is destroyed
146 def->SetParent( nullptr );
147
148 return same;
149}
150
151
152// cheap pose-independent key, so the full library comparison only runs between footprints that can be equal
153using POSE_SIGNATURE = std::tuple<size_t, size_t, size_t, long long, long long>;
154
155
156POSE_SIGNATURE poseSignature( const FOOTPRINT& aFp )
157{
158 std::unique_ptr<FOOTPRINT> pose( static_cast<FOOTPRINT*>( aFp.Clone() ) );
159
160 if( pose->IsFlipped() )
161 pose->Flip( pose->GetPosition(), FLIP_DIRECTION::TOP_BOTTOM );
162
163 pose->SetOrientation( ANGLE_0 );
164
165 BOX2I bbox = pose->GetBoundingBox( false );
166
167 return { pose->Pads().size(), pose->GraphicalItems().size(), pose->Zones().size(), (long long) bbox.GetWidth(),
168 (long long) bbox.GetHeight() };
169}
170
171
172// reuse existing row/dir only if prior import-managed cache
173bool isManagedCache( const LIBRARY_TABLE_ROW* aRow )
174{
175 return aRow && aRow->GetOptionsMap().count( IMPORT_PROJ_PROPS::MANAGED_CACHE_KEY ) > 0;
176}
177}
178
179
182 std::vector<std::unique_ptr<FOOTPRINT>> aDefinitions,
183 const wxString& aCacheNickname,
184 const std::vector<wxString>& aSourceLibNicknames )
185{
187
188 if( !aBoard )
189 return result;
190
191 // a nickname with no footprint name links to nothing, and it would name a library that does not exist
192 for( FOOTPRINT* fp : aBoard->Footprints() )
193 {
194 if( fp->GetFPID().GetLibItemName().empty() && !fp->GetFPID().GetLibNickname().empty() )
195 fp->SetFPID( LIB_ID() );
196 }
197
198 // source nickname + item name, so same-name parts from different libraries stay split
199 using SOURCE_KEY = std::pair<wxString, wxString>;
200
201 std::map<SOURCE_KEY, FOOTPRINT*> defByKey;
202 std::map<wxString, std::vector<FOOTPRINT*>> defsByName;
203
204 for( const std::unique_ptr<FOOTPRINT>& def : aDefinitions )
205 {
206 wxString name = def->GetFPID().GetUniStringLibItemName();
207
208 if( name.IsEmpty() )
209 continue;
210
211 defByKey.emplace( SOURCE_KEY( def->GetFPID().GetUniStringLibNickname(), name ), def.get() );
212 defsByName[name].push_back( def.get() );
213 }
214
215 // definitions can carry a different nickname from the placed footprints, so a unique name still
216 // matches, but an ambiguous one must not
217 auto findDef = [&]( const wxString& aNick, const wxString& aName ) -> FOOTPRINT*
218 {
219 if( auto it = defByKey.find( SOURCE_KEY( aNick, aName ) ); it != defByKey.end() )
220 return it->second;
221
222 auto byName = defsByName.find( aName );
223
224 if( byName == defsByName.end() || byName->second.size() != 1 )
225 return nullptr;
226
227 return byName->second.front();
228 };
229
230 // preload source libs before membership queries
231 for( const wxString& nick : aSourceLibNicknames )
232 {
233 if( m_adapter.GetRow( nick ) )
234 m_adapter.LoadOne( nick );
235 }
236
237 std::set<wxString> provenance( aSourceLibNicknames.begin(), aSourceLibNicknames.end() );
238
239 // resolve one source lib, empty if none or ambiguous
240 auto resolveSource = [&]( const FOOTPRINT* aFp, const wxString& aName ) -> wxString
241 {
242 std::vector<wxString> candidates;
243 wxString ownNick = aFp->GetFPID().GetUniStringLibNickname();
244
245 if( !ownNick.IsEmpty() )
246 candidates.push_back( ownNick );
247
248 for( const wxString& nick : aSourceLibNicknames )
249 {
250 if( nick != ownNick )
251 candidates.push_back( nick );
252 }
253
254 std::vector<wxString> matches;
255
256 for( const wxString& nick : candidates )
257 {
258 if( !m_adapter.GetRow( nick ) )
259 continue;
260
261 // A nickname the importer emitted is not provenance. An unrelated library that
262 // happens to carry the name must not swallow the imported definition, so it takes the
263 // link only when it holds the same footprint.
264 if( provenance.count( nick ) )
265 {
266 // some plugins throw for a footprint their library lacks
267 bool exists = false;
268
269 try
270 {
271 exists = m_adapter.FootprintExists( nick, aName );
272 }
273 catch( const IO_ERROR& )
274 {
275 }
276
277 if( !exists )
278 continue;
279 }
280 else
281 {
282 FOOTPRINT* def = findDef( ownNick, aName );
283
284 if( !def )
285 continue;
286
287 // one load answers both existence and equivalence, FootprintExists is itself a load
288 std::unique_ptr<FOOTPRINT> candidate( m_adapter.LoadFootprint( nick, aName, true ) );
289
290 if( !candidate || !sameInterface( *candidate, *def ) )
291 continue;
292 }
293
294 matches.push_back( nick );
295 }
296
297 return matches.size() == 1 ? matches.front() : wxString( wxEmptyString );
298 };
299
300 // per-instance target, empty target = cache-bound
301 std::map<SOURCE_KEY, wxString> targetByKey;
302 std::set<SOURCE_KEY> cacheKeys;
303 std::map<SOURCE_KEY, std::vector<FOOTPRINT*>> instancesByKey;
304
305 for( FOOTPRINT* fp : aBoard->Footprints() )
306 {
307 wxString name = fp->GetFPID().GetUniStringLibItemName();
308
309 if( name.IsEmpty() )
310 continue;
311
312 SOURCE_KEY key( fp->GetFPID().GetUniStringLibNickname(), name );
313
314 instancesByKey[key].push_back( fp );
315
316 if( targetByKey.count( key ) )
317 continue;
318
319 wxString sourceNick = resolveSource( fp, name );
320 targetByKey[key] = sourceNick;
321
322 if( sourceNick.IsEmpty() )
323 cacheKeys.insert( key );
324 }
325
326 // a .pretty holds one file per footprint, so the file name must be unique, and case-folded for
327 // a cache moved to Windows or macOS
328 std::set<wxString> takenFiles;
329
330 auto uniqueName = [&takenFiles]( const wxString& aName )
331 {
332 for( int suffix = 0; ; ++suffix )
333 {
334 wxString candidate = suffix ? wxString::Format( wxS( "%s_%d" ), aName, suffix ) : aName;
335
336 // the library indexes items by file name, so the item name must already be a legal one
337 ReplaceIllegalFileNameChars( candidate, '_' );
338
339 wxString fileName = candidate;
340 fileName.MakeLower();
341
342 if( takenFiles.insert( fileName ).second )
343 return candidate;
344 }
345 };
346
347 // canonical def per cache key, fall back to unique placed instance if importer gave none
348 std::map<wxString, FOOTPRINT*> cacheDefs;
349 std::map<SOURCE_KEY, wxString> cacheNameByKey;
350 std::map<const FOOTPRINT*, wxString> cacheNameByDef;
351 std::vector<std::unique_ptr<FOOTPRINT>> placedDefs;
352 std::vector<wxString> renameReports;
353
354 std::map<const FOOTPRINT*, wxString> cacheNameByInstance;
355
356 for( const SOURCE_KEY& key : cacheKeys )
357 {
358 const wxString& name = key.second;
359 FOOTPRINT* def = findDef( key.first, name );
360
361 // each placed instance must equal the copy it is linked to, or updating from the library edits the board
362 struct ITEM
363 {
364 FOOTPRINT* m_Def;
365 POSE_SIGNATURE m_Signature;
366 wxString m_CacheName;
367 };
368
369 std::vector<ITEM> items;
370
371 if( def )
372 items.push_back( { def, poseSignature( *def ), wxString() } );
373
374 for( FOOTPRINT* instance : instancesByKey[key] )
375 {
376 POSE_SIGNATURE signature = poseSignature( *instance );
377
378 auto match = std::find_if( items.begin(), items.end(),
379 [&]( const ITEM& aItem )
380 {
381 return aItem.m_Signature == signature
382 && !instance->FootprintNeedsUpdate(
383 aItem.m_Def, BOARD_ITEM::COMPARE_FLAGS::DRC )
384 && samePadOverrides( *instance, *aItem.m_Def );
385 } );
386
387 if( match == items.end() )
388 {
389 placedDefs.emplace_back( static_cast<FOOTPRINT*>( instance->Clone() ) );
390 items.push_back( { placedDefs.back().get(), signature, wxString() } );
391 match = items.end() - 1;
392 }
393
394 // the cache name is bound lazily so only items that serve an instance are published
395 if( match->m_CacheName.IsEmpty() )
396 {
397 // one definition serving several source libraries stays a single cache item
398 auto known = cacheNameByDef.find( match->m_Def );
399
400 if( known != cacheNameByDef.end() )
401 {
402 match->m_CacheName = known->second;
403 }
404 else
405 {
406 match->m_CacheName = uniqueName( name );
407
408 cacheNameByDef[match->m_Def] = match->m_CacheName;
409 cacheDefs[match->m_CacheName] = match->m_Def;
410 }
411 }
412
413 cacheNameByInstance[instance] = match->m_CacheName;
414 }
415
416 if( items.size() > 1 && !instancesByKey[key].empty() )
417 {
418 m_reporter.Report( wxString::Format( _( "Imported footprint '%s' has %zu differing placed "
419 "versions; each is kept as its own library item." ),
420 name, items.size() ),
422 }
423
424 // a definition nothing is placed from still goes in under its own name
425 if( def && cacheNameByDef.find( def ) == cacheNameByDef.end() )
426 {
427 wxString cacheName = uniqueName( name );
428
429 cacheNameByDef[def] = cacheName;
430 cacheDefs[cacheName] = def;
431 }
432
433 if( !instancesByKey[key].empty() )
434 cacheNameByKey[key] = cacheNameByInstance[instancesByKey[key].front()];
435 else if( def )
436 cacheNameByKey[key] = cacheNameByDef[def];
437
438 if( cacheNameByKey.count( key ) && cacheNameByKey[key] != name )
439 {
440 renameReports.push_back(
441 wxString::Format( _( "Imported footprint '%s' from '%s' was renamed to '%s' "
442 "in the import cache." ), name, key.first, cacheNameByKey[key] ) );
443 }
444 }
445
446 // write residuals to an atomic .pretty and register the row
447 if( !cacheDefs.empty() )
448 writeAndRegisterCache( aCacheNickname, cacheDefs, result );
449
450 // no rename happened if the cache did not publish
451 if( !result.m_cacheNickname.IsEmpty() )
452 {
453 for( const wxString& report : renameReports )
454 m_reporter.Report( report, RPT_SEVERITY_WARNING );
455 }
456
457 // re-point nicks to the resolved lib, cache-bound footprints also take their cache item name
458 for( FOOTPRINT* fp : aBoard->Footprints() )
459 {
460 LIB_ID fpid = fp->GetFPID();
461 wxString name = fpid.GetUniStringLibItemName();
462
463 if( name.IsEmpty() )
464 continue;
465
466 SOURCE_KEY key( fpid.GetUniStringLibNickname(), name );
467 auto it = targetByKey.find( key );
468
469 if( it == targetByKey.end() )
470 {
471 result.m_unresolved++;
472 continue;
473 }
474
475 // empty resolution = cache-bound, resolves only once the cache is published
476 if( it->second.IsEmpty() )
477 {
478 auto cacheName = cacheNameByInstance.find( fp );
479
480 if( cacheName == cacheNameByInstance.end() )
481 {
482 auto byKey = cacheNameByKey.find( key );
483
484 if( byKey != cacheNameByKey.end() )
485 cacheName = cacheNameByInstance.emplace( fp, byKey->second ).first;
486 }
487
488 if( result.m_cacheNickname.IsEmpty() || cacheName == cacheNameByInstance.end() )
489 {
490 result.m_unresolved++;
491 continue;
492 }
493
494 fpid.SetLibNickname( aCacheNickname );
495 fpid.SetLibItemName( cacheName->second );
496 fp->SetFPID( fpid );
497 result.m_linkedToCache++;
498 }
499 else
500 {
501 fpid.SetLibNickname( it->second );
502 fp->SetFPID( fpid );
503 result.m_linkedToSource++;
504 }
505 }
506
507 return result;
508}
509
510
512 const wxString& aCacheNickname, const std::map<wxString, FOOTPRINT*>& aCacheDefs,
514{
515 wxFileName finalFn( m_projectPath, aCacheNickname, FILEEXT::KiCadFootprintLibPathExtension );
516 wxString finalPath = finalFn.GetFullPath();
517 wxString tempPath = finalPath + wxS( ".tmp" );
518
519 // a nickname the user already owns is never repurposed, whatever its row points at
520 LIBRARY_TABLE_ROW* existingRow = m_adapter.GetRow( aCacheNickname ).value_or( nullptr );
521
522 if( existingRow && !isManagedCache( existingRow ) )
523 {
524 m_reporter.Report( wxString::Format( _( "A footprint library named '%s' is already "
525 "registered; leaving imported footprints "
526 "unresolved." ), aCacheNickname ),
528 return;
529 }
530
532
533 if( !pi )
534 {
535 m_reporter.Report( _( "Cannot reconcile imported footprints: no KiCad footprint "
536 "writer." ), RPT_SEVERITY_ERROR );
537 return;
538 }
539
540 // best-effort cleanup, must not throw
541 auto safeDelete = [&pi]( const wxString& aPath )
542 {
543 try
544 {
545 pi->DeleteLibrary( aPath );
546 }
547 catch( const IO_ERROR& )
548 {
549 }
550 };
551
552 bool wrote = false;
553
554 try
555 {
556 if( wxDir::Exists( tempPath ) )
557 pi->DeleteLibrary( tempPath );
558
559 pi->CreateLibrary( tempPath );
560
561 // without this every save re-parses the whole library written so far
562 std::map<std::string, UTF8> properties { { "skip_cache_validation", "" } };
563
564 // the definitions are ours to consume and FootprintSave copies what it keeps
565 for( const auto& [name, def] : aCacheDefs )
566 {
567 LIB_ID id = def->GetFPID();
568
569 id.SetLibNickname( aCacheNickname );
570 id.SetLibItemName( name );
571 def->SetFPID( id );
572 def->SetReference( wxS( "REF**" ) );
573 pi->FootprintSave( tempPath, def, &properties );
574 }
575
576 wrote = true;
577 }
578 catch( const IO_ERROR& ioe )
579 {
580 m_reporter.Report( wxString::Format( _( "Error writing imported footprint cache "
581 "'%s': %s" ), aCacheNickname, ioe.What() ),
583 }
584
585 if( !wrote )
586 {
587 if( wxDir::Exists( tempPath ) )
588 safeDelete( tempPath );
589
590 return;
591 }
592
593 // publish temp->final, replace only a managed cache, never a user lib
594 if( wxDir::Exists( finalPath ) )
595 {
596 if( isManagedCache( existingRow ) )
597 {
598 safeDelete( finalPath );
599 }
600 else
601 {
602 m_reporter.Report( wxString::Format( _( "A library already exists at '%s'; leaving "
603 "imported footprints unresolved." ),
604 finalPath ),
606 safeDelete( tempPath );
607 return;
608 }
609 }
610
611 if( !wxRenameFile( tempPath, finalPath, false ) )
612 {
613 m_reporter.Report( wxString::Format( _( "Could not publish imported footprint cache to "
614 "'%s'." ), finalPath ),
616 safeDelete( tempPath );
617 return;
618 }
619
620 // only claim the cache when its table row is registered, else FPIDs re-point to a dead nickname
621 if( !registerCacheRow( aCacheNickname ) )
622 return;
623
624 aResult.m_cacheNickname = aCacheNickname;
625 aResult.m_savedToCache = static_cast<int>( aCacheDefs.size() );
626}
627
628
629bool FOOTPRINT_IMPORT_RECONCILER::registerCacheRow( const wxString& aCacheNickname )
630{
631 std::optional<LIBRARY_TABLE*> tableOpt = m_adapter.ProjectTable();
632
633 if( !tableOpt || !*tableOpt )
634 {
635 m_reporter.Report( _( "Cannot register imported footprint cache: no project library "
636 "table." ), RPT_SEVERITY_ERROR );
637 return false;
638 }
639
640 LIBRARY_TABLE* table = *tableOpt;
641 wxString cacheFile = aCacheNickname + wxS( "." )
643 wxString uri = wxS( "${KIPRJMOD}/" ) + cacheFile;
644 LIBRARY_TABLE_ROW* row = table->HasRow( aCacheNickname )
645 ? table->Row( aCacheNickname ).value_or( nullptr )
646 : &table->InsertRow();
647
648 if( !row )
649 return false;
650
651 row->SetNickname( aCacheNickname );
652 row->SetURI( uri );
653 row->SetType( wxS( "KiCad" ) );
656
657 // an unsaved row is gone on restart, so the cache cannot be claimed
658 if( !table->Save() )
659 {
660 m_reporter.Report( _( "Error saving project footprint library table; imported footprints "
661 "left unresolved." ), RPT_SEVERITY_ERROR );
662 return false;
663 }
664
665 // load the cache so membership and the updater resolve it
666 m_adapter.LoadOne( aCacheNickname );
667 return true;
668}
669
670
672ReconcileImportedFootprints( std::vector<std::unique_ptr<FOOTPRINT>> aDefinitions, BOARD& aBoard,
673 PROJECT& aProject, const wxString& aBoardPath,
674 const std::map<std::string, UTF8>* aProperties, REPORTER& aReporter )
675{
677
678 // an importer that publishes nothing still reconciles, the placed footprints are the fallback
680
681 if( !adapter )
682 return result;
683
684 // manager pre-commits the cache nickname + source libs; standalone import derives from filename
685 wxString cacheNick;
686 std::vector<wxString> sourceLibs;
687 IMPORT_PROJ_PROPS::ReadFootprintProps( aProperties, cacheNick, sourceLibs );
688
689 if( cacheNick.IsEmpty() )
690 cacheNick = IMPORT_PROJ_PROPS::MakeCacheNickname( wxFileName( aBoardPath ).GetName() );
691
692 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, aProject.GetProjectPath(), aReporter );
693
694 // reconciliation failure must not abort the import
695 try
696 {
697 result = reconciler.Reconcile( &aBoard, std::move( aDefinitions ), cacheNick, sourceLibs );
698 }
699 catch( const IO_ERROR& ioe )
700 {
701 aReporter.Report( wxString::Format( _( "Could not reconcile imported footprint libraries: "
702 "%s" ), ioe.What() ), RPT_SEVERITY_ERROR );
703 }
704
705 return result;
706}
707
708
710ReconcileImportedFootprints( PCB_IO& aPlugin, BOARD& aBoard, PROJECT& aProject,
711 const wxString& aBoardPath,
712 const std::map<std::string, UTF8>* aProperties, REPORTER& aReporter )
713{
714 std::vector<std::unique_ptr<FOOTPRINT>> definitions;
715
716 try
717 {
718 for( FOOTPRINT* footprint : aPlugin.GetImportedCachedLibraryFootprints() )
719 definitions.emplace_back( footprint );
720 }
721 catch( const IO_ERROR& )
722 {
723 // importer retains no definitions, the placed footprints are the fallback
724 }
725
726 return ReconcileImportedFootprints( std::move( definitions ), aBoard, aProject, aBoardPath,
727 aProperties, aReporter );
728}
729
730
731std::map<wxString, wxString> ImportedFootprintIds( const BOARD& aBoard )
732{
733 std::map<wxString, wxString> ids;
734 std::set<wxString> repeated;
735
736 for( const FOOTPRINT* footprint : aBoard.Footprints() )
737 {
738 const wxString& ref = footprint->GetReference();
739
740 if( ref.IsEmpty() || footprint->GetFPID().GetLibItemName().empty() )
741 continue;
742
743 if( !ids.emplace( ref, footprint->GetFPID().Format().wx_str() ).second )
744 repeated.insert( ref );
745 }
746
747 for( const wxString& ref : repeated )
748 ids.erase( ref );
749
750 return ids;
751}
const char * name
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
virtual const BOARD * GetBoard() const
Return the BOARD in which this BOARD_ITEM resides, or NULL if none.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
const FOOTPRINTS & Footprints() const
Definition board.h:465
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr size_type GetHeight() const
Definition box2.h:212
Frame-independent, non-interactive service that reconciles the footprint-library references of a fres...
void writeAndRegisterCache(const wxString &aCacheNickname, const std::map< wxString, FOOTPRINT * > &aCacheDefs, FOOTPRINT_IMPORT_RECONCILE_RESULT &aResult)
Write the residual definitions into an atomically-published .pretty and register its row.
FOOTPRINT_IMPORT_RECONCILER(FOOTPRINT_LIBRARY_ADAPTER &aAdapter, const wxString &aProjectPath, REPORTER &aReporter=NULL_REPORTER::GetInstance())
FOOTPRINT_IMPORT_RECONCILE_RESULT Reconcile(BOARD *aBoard, std::vector< std::unique_ptr< FOOTPRINT > > aDefinitions, const wxString &aCacheNickname, const std::vector< wxString > &aSourceLibNicknames)
Reconcile aBoard against the importer definitions and the provenance source libraries.
FOOTPRINT_LIBRARY_ADAPTER & m_adapter
bool registerCacheRow(const wxString &aCacheNickname)
Insert or refresh the project footprint-library-table row for the generated cache.
An interface to the global shared library manager that is schematic-specific and linked to one projec...
EDA_ITEM * Clone() const override
Create a duplicate of this item with linked list members set to NULL.
std::deque< PAD * > & Pads()
Definition footprint.h:355
bool IsFlipped() const
Definition footprint.h:620
const LIB_ID & GetFPID() const
Definition footprint.h:417
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()
void SetOptions(const wxString &aOptions)
void SetNickname(const wxString &aNickname)
void SetType(const wxString &aType)
std::map< std::string, UTF8 > GetOptionsMap() const
void SetURI(const wxString &aUri)
void SetScope(LIBRARY_TABLE_SCOPE aScope)
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
int SetLibItemName(const UTF8 &aLibItemName)
Override the library item name portion of the LIB_ID to aLibItemName.
Definition lib_id.cpp:124
int SetLibNickname(const UTF8 &aLibNickname)
Override the logical library name portion of the LIB_ID to aLibNickname.
Definition lib_id.cpp:113
const wxString GetUniStringLibItemName() const
Get strings for display messages in dialogs.
Definition lib_id.h:108
const wxString GetUniStringLibNickname() const
Definition lib_id.h:84
A PADSTACK defines the characteristics of a single or multi-layer pad, in the IPC sense of the word.
Definition padstack.h:156
COPPER_LAYER_PROPS & CopperLayer(PCB_LAYER_ID aLayer)
EDA_ANGLE ThermalSpokeAngle(PCB_LAYER_ID aLayer=F_Cu) const
PAD_SHAPE Shape(PCB_LAYER_ID aLayer) const
Definition padstack.cpp:876
PAD_SHAPE AnchorShape(PCB_LAYER_ID aLayer) const
Definition padstack.cpp:918
Definition pad.h:61
@ KICAD_SEXP
S-expression Pcbnew file format.
Definition pcb_io_mgr.h:54
static PCB_IO * FindPlugin(PCB_FILE_T aFileType)
Return a PCB_IO which the caller can use to import, export, save, or load design documents.
A base class that BOARD loading and saving plugins should derive from.
Definition pcb_io.h:78
virtual std::vector< FOOTPRINT * > GetImportedCachedLibraryFootprints()
Return a container with the cached library footprints generated in the last call to Load.
Definition pcb_io.cpp:99
static FOOTPRINT_LIBRARY_ADAPTER * FootprintLibAdapter(PROJECT *aProject)
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
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
static bool empty(const wxTextEntryBase *aCtrl)
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:448
std::map< wxString, wxString > ImportedFootprintIds(const BOARD &aBoard)
Map each board reference to the full LIB_ID of its footprint.
FOOTPRINT_IMPORT_RECONCILE_RESULT ReconcileImportedFootprints(std::vector< std::unique_ptr< FOOTPRINT > > aDefinitions, BOARD &aBoard, PROJECT &aProject, const wxString &aBoardPath, const std::map< std::string, UTF8 > *aProperties, REPORTER &aReporter)
Reconcile aBoard against the definitions an importer retained while loading it.
static const std::string KiCadFootprintLibPathExtension
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
Definition io_mgr.h:33
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ TOP_BOTTOM
Flip top to bottom (around the X axis)
Definition mirror.h:25
wxString ManagedCacheOption()
Options string identifying a library-table row as a generated import cache.
constexpr char MANAGED_CACHE_KEY[]
Library-table row option key marking a row as a generated import cache.
wxString MakeCacheNickname(const wxString &aStem)
Derive the generated footprint-cache nickname from a project or file stem.
void ReadFootprintProps(const std::map< std::string, UTF8 > *aProps, wxString &aCacheNickname, std::vector< wxString > &aSourceFpLibs)
Read the footprint-import coordination properties out of a properties map.
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_INFO
bool ReplaceIllegalFileNameChars(std::string &aName, int aReplaceChar)
Checks aName for illegal file name characters.
Outcome of a post-import footprint-library reconciliation pass.
int m_savedToCache
distinct definitions written into the cache library
wxString m_cacheNickname
nickname of the generated cache, empty if none written
The features of a padstack that can vary between copper layers All parameters are optional; leaving t...
Definition padstack.h:229
std::optional< ZONE_CONNECTION > zone_connection
Definition padstack.h:231
std::optional< int > thermal_spoke_width
Definition padstack.h:232
std::optional< int > clearance
Definition padstack.h:235
std::optional< int > thermal_gap
Definition padstack.h:234
static std::vector< int > candidates(const SEGMENT_INDEX &aIndex, const SEG &aQuery, int aPadding)
wxString result
Test unit parsing edge cases and error handling.
Definition of file extensions used in Kicad.