KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcb_io_altium_designer.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) 2019 Thomas Pointhuber <[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
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU 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
24
25#include <set>
26
27#include <wx/string.h>
28
29#include <font/fontconfig.h>
30#include <project.h>
32#include <altium_pcb.h>
34#include <io/io_utils.h>
37#include <pcb_io/pcb_io.h>
38#include <reporter.h>
39
40#include <board.h>
41#include <footprint.h>
42
43#include <compoundfilereader.h>
44#include <utf.h>
45
46
48 const std::vector<ALTIUM_PROJECT_VARIANT>& aVariants )
49{
50 std::map<wxString, FOOTPRINT*> fpByRef;
51 std::map<KIID, std::vector<FOOTPRINT*>> fpByUid;
52
53 for( FOOTPRINT* fp : aBoard->Footprints() )
54 {
55 fpByRef[fp->GetReference()] = fp;
56
57 // The importer stores the component unique id as the last path element. Repeated
58 // channels share one id across footprints, so collect all of them to detect ambiguity.
59 const KIID_PATH& path = fp->GetPath();
60
61 if( path.size() >= 2 )
62 fpByUid[path.back()].push_back( fp );
63 }
64
65 for( const ALTIUM_PROJECT_VARIANT& pv : aVariants )
66 {
67 aBoard->AddVariant( pv.name );
68
69 if( !pv.description.empty() && pv.description != pv.name )
70 aBoard->SetVariantDescription( pv.name, pv.description );
71
72 for( const ALTIUM_VARIANT_ENTRY& entry : pv.variations )
73 {
74 FOOTPRINT* target = nullptr;
75
76 // Prefer unique-id matching, but only when it resolves to a single footprint. A
77 // shared id (repeated channels) is ambiguous, so fall back to the designator.
78 if( !entry.uniqueId.empty() )
79 {
80 auto it = fpByUid.find( AltiumUniqueIdToKiid( entry.uniqueId ) );
81
82 if( it != fpByUid.end() && it->second.size() == 1 )
83 target = it->second.front();
84 }
85
86 if( !target )
87 {
88 auto it = fpByRef.find( entry.designator );
89
90 if( it != fpByRef.end() )
91 target = it->second;
92 }
93
94 if( !target )
95 continue;
96
97 FOOTPRINT_VARIANT* fpVariant = target->AddVariant( pv.name );
98
99 if( !fpVariant )
100 continue;
101
102 if( entry.kind == 1 )
103 {
104 fpVariant->SetDNP( true );
105 fpVariant->SetExcludedFromBOM( true );
106 fpVariant->SetExcludedFromPosFiles( true );
107 }
108 else if( entry.kind == 0 )
109 {
110 for( const auto& [key, value] : entry.alternateFields )
111 {
112 if( key.CmpNoCase( wxS( "LibReference" ) ) == 0 )
113 fpVariant->SetFieldValue( wxS( "Value" ), value );
114 else if( key.CmpNoCase( wxS( "Description" ) ) == 0 )
115 fpVariant->SetFieldValue( wxS( "Description" ), value );
116 else if( key.CmpNoCase( wxS( "Footprint" ) ) == 0 )
117 fpVariant->SetFieldValue( wxS( "Footprint" ), value );
118 }
119 }
120 }
121 }
122}
123
125 const std::map<wxString, wxString>& aParameters )
126{
127 if( !aProject )
128 return;
129
130 // Names KiCad resolves contextually (board fields, title block, special strings). Importing
131 // them as project variables would shadow nothing useful and could surprise the user, so they
132 // are left to native resolution.
133 static const std::set<wxString> reserved = {
134 wxS( "LAYER" ), wxS( "FILENAME" ), wxS( "FILEPATH" ),
135 wxS( "PROJECTNAME" ), wxS( "VARIANT" ), wxS( "VARIANT_DESC" ),
136 wxS( "ISSUE_DATE" ), wxS( "CURRENT_DATE" ), wxS( "CURRENT_TIME_LOCALE" ),
137 wxS( "CURRENT_TIME_HH_MM_SS" ), wxS( "REVISION" ), wxS( "TITLE" ),
138 wxS( "COMPANY" ), wxS( "COMMENT1" ), wxS( "COMMENT2" ),
139 wxS( "COMMENT3" ), wxS( "COMMENT4" ), wxS( "COMMENT5" ),
140 wxS( "COMMENT6" ), wxS( "COMMENT7" ), wxS( "COMMENT8" ),
141 wxS( "COMMENT9" ), wxS( "VCSHASH" ), wxS( "VCSSHORTHASH" ),
142 wxS( "KICAD_VERSION" ), wxS( "PAPER" ), wxS( "SHEETNAME" ),
143 wxS( "SHEETPATH" ), wxS( "DRC_WARNING" ), wxS( "DRC_ERROR" ),
144 wxS( "ERC_WARNING" ), wxS( "ERC_ERROR" )
145 };
146
147 std::map<wxString, wxString>& textVars = aProject->GetTextVars();
148 bool added = false;
149
150 for( const auto& [name, value] : aParameters )
151 {
152 if( reserved.count( name ) )
153 continue;
154
155 // Don't clobber a variable the user (or a prior import step) already set.
156 if( textVars.count( name ) )
157 continue;
158
159 textVars[name] = value;
160 added = true;
161 }
162
163 if( added )
164 aProject->IncrementTextVarsTicker();
165}
166
167
173
174
178
179
181 const std::vector<INPUT_LAYER_DESC>& aInputLayerDescriptionVector )
182{
183 std::map<wxString, PCB_LAYER_ID> retval;
184
185 // Just return a the auto-mapped layers
186 for( INPUT_LAYER_DESC layerDesc : aInputLayerDescriptionVector )
187 {
188 retval.insert( { layerDesc.Name, layerDesc.AutoMapLayer } );
189 }
190
191 return retval;
192}
193
194
195bool PCB_IO_ALTIUM_DESIGNER::checkFileHeader( const wxString& aFileName )
196{
197 // Compound File Binary Format header
199}
200
201
202bool PCB_IO_ALTIUM_DESIGNER::CanReadBoard( const wxString& aFileName ) const
203{
204 if( !PCB_IO::CanReadBoard( aFileName ) )
205 return false;
206
207 return checkFileHeader( aFileName );
208}
209
210
211bool PCB_IO_ALTIUM_DESIGNER::CanReadLibrary( const wxString& aFileName ) const
212{
213 if( !PCB_IO::CanReadLibrary( aFileName ) )
214 return false;
215
216 return checkFileHeader( aFileName );
217}
218
219
220BOARD* PCB_IO_ALTIUM_DESIGNER::LoadBoard( const wxString& aFileName, BOARD* aAppendToMe,
221 const std::map<std::string, UTF8>* aProperties, PROJECT* aProject )
222{
223
224 m_props = aProperties;
225
226 m_board = aAppendToMe ? aAppendToMe : new BOARD();
227
228 // Collect the font substitution warnings (RAII - automatically reset on scope exit)
230
231 // Give the filename to the board if it's new
232 if( !aAppendToMe )
233 m_board->SetFileName( aFileName );
234
235 // clang-format off
236 const std::map<ALTIUM_PCB_DIR, std::string> mapping = {
237 { ALTIUM_PCB_DIR::FILE_HEADER, "FileHeader" },
238 { ALTIUM_PCB_DIR::ARCS6, "Arcs6" },
239 { ALTIUM_PCB_DIR::BOARD6, "Board6" },
240 { ALTIUM_PCB_DIR::BOARDREGIONS, "BoardRegions" },
241 { ALTIUM_PCB_DIR::CLASSES6, "Classes6" },
242 { ALTIUM_PCB_DIR::COMPONENTS6, "Components6" },
243 { ALTIUM_PCB_DIR::COMPONENTBODIES6, "ComponentBodies6" },
244 { ALTIUM_PCB_DIR::DIMENSIONS6, "Dimensions6" },
245 { ALTIUM_PCB_DIR::EXTENDPRIMITIVEINFORMATION, "ExtendedPrimitiveInformation" },
246 { ALTIUM_PCB_DIR::FILLS6, "Fills6" },
247 { ALTIUM_PCB_DIR::MODELS, "Models" },
248 { ALTIUM_PCB_DIR::NETS6, "Nets6" },
249 { ALTIUM_PCB_DIR::PADS6, "Pads6" },
250 { ALTIUM_PCB_DIR::POLYGONS6, "Polygons6" },
251 { ALTIUM_PCB_DIR::REGIONS6, "Regions6" },
252 { ALTIUM_PCB_DIR::RULES6, "Rules6" },
253 { ALTIUM_PCB_DIR::SHAPEBASEDREGIONS6, "ShapeBasedRegions6" },
254 { ALTIUM_PCB_DIR::SMARTUNIONS, "SmartUnions" },
255 { ALTIUM_PCB_DIR::TEXTS6, "Texts6" },
256 { ALTIUM_PCB_DIR::TRACKS6, "Tracks6" },
257 { ALTIUM_PCB_DIR::VIAS6, "Vias6" },
258 { ALTIUM_PCB_DIR::WIDESTRINGS6, "WideStrings6" }
259 };
260 // clang-format on
261
262 ALTIUM_PCB_COMPOUND_FILE altiumPcbFile( aFileName );
263
264 try
265 {
266 // Parse File
268 pcb.Parse( altiumPcbFile, mapping );
269 }
270 catch( CFB::CFBException& exception )
271 {
272 THROW_IO_ERROR( exception.what() );
273 }
274
275 if( m_props && m_props->count( "project_file" ) )
276 {
277 const wxString& projectFile = m_props->at( "project_file" );
278
279 auto variants = ParseAltiumProjectVariants( projectFile );
280
281 if( !variants.empty() )
283
285 ParseAltiumProjectParameters( projectFile ) );
286 }
287
288 return m_board;
289}
290
291
292long long PCB_IO_ALTIUM_DESIGNER::GetLibraryTimestamp( const wxString& aLibraryPath ) const
293{
294 // File hasn't been loaded yet.
295 if( aLibraryPath.IsEmpty() )
296 return 0;
297
298 wxFileName fn( aLibraryPath );
299
300 if( fn.IsFileReadable() && fn.GetModificationTime().IsValid() )
301 return fn.GetModificationTime().GetValue().GetValue();
302 else
303 return 0;
304}
305
306
307void PCB_IO_ALTIUM_DESIGNER::loadAltiumLibrary( const wxString& aLibraryPath )
308{
309 // Suppress font substitution warnings (RAII - automatically restored on scope exit)
310 FONTCONFIG_REPORTER_SCOPE fontconfigScope( nullptr );
311
312 long long timestamp = GetLibraryTimestamp( aLibraryPath );
313
314 if( m_fplibFiles.contains( aLibraryPath ) && m_fplibFiles[aLibraryPath].m_Timestamp == timestamp )
315 return; // Already loaded
316
317 try
318 {
319 ALTIUM_FILE_CACHE& libFiles = m_fplibFiles[aLibraryPath];
320
321 if( aLibraryPath.Lower().EndsWith( wxS( ".pcblib" ) ) )
322 {
323 libFiles.m_Files.emplace_back( std::make_unique<ALTIUM_PCB_COMPOUND_FILE>( aLibraryPath ) );
324 }
325 else if( aLibraryPath.Lower().EndsWith( wxS( ".intlib" ) ) )
326 {
327 std::unique_ptr<ALTIUM_PCB_COMPOUND_FILE> lib = std::make_unique<ALTIUM_PCB_COMPOUND_FILE>( aLibraryPath );
328
329 for( const auto& [pcbLibName, pcbCfe] : lib->EnumDir( L"PCBLib" ) )
330 {
331 std::unique_ptr<ALTIUM_PCB_COMPOUND_FILE> libFile = std::make_unique<ALTIUM_PCB_COMPOUND_FILE>();
332
333 if( lib->DecodeIntLibStream( *pcbCfe, libFile.get() ) )
334 libFiles.m_Files.emplace_back( std::move( libFile ) );
335 }
336 }
337
338 libFiles.m_Timestamp = timestamp;
339 }
340 catch( CFB::CFBException& exception )
341 {
342 THROW_IO_ERROR( exception.what() );
343 }
344}
345
346
347void PCB_IO_ALTIUM_DESIGNER::FootprintEnumerate( wxArrayString& aFootprintNames,
348 const wxString& aLibraryPath, bool aBestEfforts,
349 const std::map<std::string, UTF8>* aProperties )
350{
351 loadAltiumLibrary( aLibraryPath );
352
353 if( !m_fplibFiles.contains( aLibraryPath ) )
354 return; // No footprint libraries in file, ignore.
355
356 try
357 {
358 for( std::unique_ptr<ALTIUM_PCB_COMPOUND_FILE>& altiumLibFile : m_fplibFiles[aLibraryPath].m_Files )
359 {
360 // Map code-page-dependent names to unicode names
361 CASE_INSENSITIVE_MAP<wxString> patternMap = altiumLibFile->ListLibFootprints();
362
363 const std::vector<std::string> streamName = { "Library", "Data" };
364 const CFB::COMPOUND_FILE_ENTRY* libraryData = altiumLibFile->FindStream( streamName );
365
366 if( libraryData == nullptr )
367 THROW_IO_ERRORF( _( "File not found: '%s'." ), FormatPath( streamName ) );
368
369 ALTIUM_BINARY_PARSER parser( *altiumLibFile, libraryData );
370
371 std::map<wxString, wxString> properties = parser.ReadProperties();
372
373 uint32_t numberOfFootprints = parser.Read<uint32_t>();
374
375 // The number of footprints might be bogus. Following it is a list of index strings to
376 // find the footprints, and this list might actually be longer than how many footprints
377 // actually are in the library.
378 // If this case was detected, truncate the list to the number of footprints.
379 // Sample in #18452 shows that by simply truncating can get the correct footprints.
380 // Fixes https://gitlab.com/kicad/code/kicad/issues/18452.
381 // After truncation we shall no longer detect if the stream is fully parsed.
382 bool footprintListNotTruncated = true;
383
384 if ( patternMap.size() < numberOfFootprints )
385 {
386 numberOfFootprints = patternMap.size();
387 footprintListNotTruncated = false;
388 }
389
390 aFootprintNames.Alloc( numberOfFootprints );
391
392 for( size_t i = 0; i < numberOfFootprints; i++ )
393 {
395
396 wxScopedCharBuffer charBuffer = parser.ReadCharBuffer();
397 wxString fpPattern( charBuffer, wxConvISO8859_1 );
398
399 auto it = patternMap.find( fpPattern );
400
401 if( it != patternMap.end() )
402 aFootprintNames.Add( it->second ); // Proper unicode name
403 else
404 THROW_IO_ERRORF( _( "Component name not found: '%s'" ), fpPattern );
405
406 parser.SkipSubrecord();
407 }
408
409 if( parser.HasParsingError() )
410 THROW_IO_ERRORF( wxT( "%s stream was not parsed correctly" ), FormatPath( streamName ) );
411
412 if( footprintListNotTruncated && parser.GetRemainingBytes() != 0 )
413 THROW_IO_ERRORF( wxT( "%s stream is not fully parsed" ), FormatPath( streamName ) );
414 }
415 }
416 catch( CFB::CFBException& exception )
417 {
418 THROW_IO_ERROR( exception.what() );
419 }
420}
421
422
424 const wxString& aFootprintName, bool aKeepUUID,
425 const std::map<std::string, UTF8>* aProperties )
426{
427 loadAltiumLibrary( aLibraryPath );
428
429 if( !m_fplibFiles.contains( aLibraryPath ) )
430 THROW_IO_ERRORF( _( "No footprints in library '%s'" ), aLibraryPath );
431
432 try
433 {
434 for( std::unique_ptr<ALTIUM_PCB_COMPOUND_FILE>& altiumLibFile : m_fplibFiles[aLibraryPath].m_Files )
435 {
436 altiumLibFile->CacheLibModels();
437 auto [dirName, fpCfe] = altiumLibFile->FindLibFootprintDirName( aFootprintName );
438
439 if( dirName.IsEmpty() )
440 continue;
441
442 // Parse File
443 ALTIUM_PCB pcb( m_board, nullptr, m_layer_mapping_handler, m_reporter, aLibraryPath, aFootprintName );
444 return pcb.ParseFootprint( *altiumLibFile, aFootprintName );
445 }
446 }
447 catch( CFB::CFBException& exception )
448 {
449 THROW_IO_ERROR( exception.what() );
450 }
451
452 THROW_IO_ERRORF( _( "Footprint '%s' not found in '%s'." ), aFootprintName, aLibraryPath );
453}
454
455
457{
458 std::vector<FOOTPRINT*> footprints;
459
460 // caller owns result, clone not alias
461 for( FOOTPRINT* fp : m_board->Footprints() )
462 footprints.push_back( static_cast<FOOTPRINT*>( fp->Clone() ) );
463
464 return footprints;
465}
const char * name
std::string FormatPath(const std::vector< std::string > &aVectorPath)
Helper for debug logging (vector -> string)
@ EXTENDPRIMITIVEINFORMATION
Definition altium_pcb.h:52
std::vector< ALTIUM_PROJECT_VARIANT > ParseAltiumProjectVariants(const wxString &aPrjPcbPath)
Parse all [ProjectVariantN] sections from an Altium .PrjPcb project file.
std::map< wxString, wxString > ParseAltiumProjectParameters(const wxString &aPrjPcbPath)
Parse all [ParameterN] sections from an Altium .PrjPcb project file.
KIID AltiumUniqueIdToKiid(const wxString &aUniqueId)
Derive a stable KIID from an Altium component unique id.
std::map< wxString, ValueType, DETAIL::CASE_INSENSITIVE_COMPARER > CASE_INSENSITIVE_MAP
wxScopedCharBuffer ReadCharBuffer()
std::map< wxString, wxString > ReadProperties(std::function< std::map< wxString, wxString >(const std::string &)> handleBinaryData=[](const std::string &) { return std::map< wxString, wxString >();})
FOOTPRINT * ParseFootprint(ALTIUM_PCB_COMPOUND_FILE &altiumLibFile, const wxString &aFootprintName)
void Parse(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const std::map< ALTIUM_PCB_DIR, std::string > &aFileMapping)
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:373
void AddVariant(const wxString &aVariantName)
Definition board.cpp:2981
const FOOTPRINTS & Footprints() const
Definition board.h:421
void SetVariantDescription(const wxString &aVariantName, const wxString &aDescription)
Definition board.cpp:3087
RAII class to set and restore the fontconfig reporter.
Definition reporter.h:368
Variant information for a footprint.
Definition footprint.h:215
void SetExcludedFromPosFiles(bool aExclude)
Definition footprint.h:235
void SetDNP(bool aDNP)
Definition footprint.h:229
void SetFieldValue(const wxString &aFieldName, const wxString &aValue)
Set a field value override for this variant.
Definition footprint.h:257
void SetExcludedFromBOM(bool aExclude)
Definition footprint.h:232
FOOTPRINT_VARIANT * AddVariant(const wxString &aVariantName)
Add a new variant with the given name.
REPORTER * m_reporter
Reporter to log errors/warnings to, may be nullptr.
Definition io_base.h:238
PROGRESS_REPORTER * m_progressReporter
Progress reporter to track the progress of the operation, may be nullptr.
Definition io_base.h:241
virtual bool CanReadLibrary(const wxString &aFileName) const
Checks if this IO object can read the specified library file/directory.
Definition io_base.cpp:71
virtual void RegisterCallback(LAYER_MAPPING_HANDLER aLayerMappingHandler)
Register a different handler to be called when mapping of input layers to KiCad layers occurs.
LAYER_MAPPING_HANDLER m_layer_mapping_handler
Callback to get layer mapping.
static LOAD_INFO_REPORTER & GetInstance()
Definition reporter.cpp:306
long long GetLibraryTimestamp(const wxString &aLibraryPath) const override
Generate a timestamp representing all the files in the library (including the library directory).
FOOTPRINT * FootprintLoad(const wxString &aLibraryPath, const wxString &aFootprintName, bool aKeepUUID=false, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Load a footprint having aFootprintName from the aLibraryPath containing a library format that this PC...
void FootprintEnumerate(wxArrayString &aFootprintNames, const wxString &aLibraryPath, bool aBestEfforts, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Return a list of footprint names contained within the library at aLibraryPath.
std::vector< FOOTPRINT * > GetImportedCachedLibraryFootprints() override
Return a container with the cached library footprints generated in the last call to Load.
static bool checkFileHeader(const wxString &aFileName)
bool CanReadBoard(const wxString &aFileName) const override
Checks if this PCB_IO can read the specified board file.
void loadAltiumLibrary(const wxString &aLibraryPath)
static std::map< wxString, PCB_LAYER_ID > DefaultLayerMappingCallback(const std::vector< INPUT_LAYER_DESC > &aInputLayerDescriptionVector)
Return the automapped layers.
std::map< wxString, ALTIUM_FILE_CACHE > m_fplibFiles
BOARD * LoadBoard(const wxString &aFileName, BOARD *aAppendToMe, const std::map< std::string, UTF8 > *aProperties, PROJECT *aProject=nullptr) override
Load information from some input file format that this PCB_IO implementation knows about into either ...
bool CanReadLibrary(const wxString &aFileName) const override
Checks if this IO object can read the specified library file/directory.
BOARD * m_board
The board BOARD being worked on, no ownership here.
Definition pcb_io.h:349
virtual bool CanReadBoard(const wxString &aFileName) const
Checks if this PCB_IO can read the specified board file.
Definition pcb_io.cpp:38
PCB_IO(const wxString &aName)
Definition pcb_io.h:342
const std::map< std::string, UTF8 > * m_props
Properties passed via Save() or Load(), no ownership, may be NULL.
Definition pcb_io.h:352
Container for project specific data.
Definition project.h:63
void IncrementTextVarsTicker()
Definition project.h:112
virtual std::map< wxString, wxString > & GetTextVars() const
Definition project.cpp:126
#define _(s)
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
#define THROW_IO_ERRORF(msg,...)
const std::vector< uint8_t > COMPOUND_FILE_HEADER
Definition io_utils.cpp:29
bool fileHasBinaryHeader(const wxString &aFilePath, const std::vector< uint8_t > &aHeader, size_t aOffset)
Check if a file starts with a defined binary header.
Definition io_utils.cpp:57
void ApplyAltiumProjectVariantsToBoard(BOARD *aBoard, const std::vector< ALTIUM_PROJECT_VARIANT > &aVariants)
Pcbnew PLUGIN for Altium *.PcbDoc format.
void ApplyAltiumProjectParametersToProject(PROJECT *aProject, const std::map< wxString, wxString > &aParameters)
Register Altium project parameters as KiCad project text variables so that imported board text refere...
void ApplyAltiumProjectVariantsToBoard(BOARD *aBoard, const std::vector< ALTIUM_PROJECT_VARIANT > &aVariants)
Apply parsed Altium project variants to a board by setting FOOTPRINT_VARIANT data on each footprint w...
void ApplyAltiumProjectParametersToProject(PROJECT *aProject, const std::map< wxString, wxString > &aParameters)
Register Altium project parameters as KiCad project text variables so that imported board text refere...
A project-level assembly variant parsed from an Altium .PrjPcb file.
A single component variation within an Altium project variant.
int kind
wxString uniqueId
wxString designator
std::map< wxString, wxString > alternateFields
Describes an imported layer and how it could be mapped to KiCad Layers.
std::vector< std::unique_ptr< ALTIUM_PCB_COMPOUND_FILE > > m_Files
std::string path