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
220void PCB_IO_ALTIUM_DESIGNER::loadBoard( const wxString& aFileName, BOARD& aBoard, bool aIsNewLoad,
221 const std::map<std::string, UTF8>* aProperties, PROJECT* aProject )
222{
223 m_props = aProperties;
224 m_board = &aBoard;
225
226 // Collect the font substitution warnings (RAII - automatically reset on scope exit)
228
229 // clang-format off
230 const std::map<ALTIUM_PCB_DIR, std::string> mapping = {
231 { ALTIUM_PCB_DIR::FILE_HEADER, "FileHeader" },
232 { ALTIUM_PCB_DIR::ARCS6, "Arcs6" },
233 { ALTIUM_PCB_DIR::BOARD6, "Board6" },
234 { ALTIUM_PCB_DIR::BOARDREGIONS, "BoardRegions" },
235 { ALTIUM_PCB_DIR::CLASSES6, "Classes6" },
236 { ALTIUM_PCB_DIR::COMPONENTS6, "Components6" },
237 { ALTIUM_PCB_DIR::COMPONENTBODIES6, "ComponentBodies6" },
238 { ALTIUM_PCB_DIR::DIMENSIONS6, "Dimensions6" },
239 { ALTIUM_PCB_DIR::EXTENDPRIMITIVEINFORMATION, "ExtendedPrimitiveInformation" },
240 { ALTIUM_PCB_DIR::FILLS6, "Fills6" },
241 { ALTIUM_PCB_DIR::MODELS, "Models" },
242 { ALTIUM_PCB_DIR::NETS6, "Nets6" },
243 { ALTIUM_PCB_DIR::PADS6, "Pads6" },
244 { ALTIUM_PCB_DIR::POLYGONS6, "Polygons6" },
245 { ALTIUM_PCB_DIR::REGIONS6, "Regions6" },
246 { ALTIUM_PCB_DIR::RULES6, "Rules6" },
247 { ALTIUM_PCB_DIR::SHAPEBASEDREGIONS6, "ShapeBasedRegions6" },
248 { ALTIUM_PCB_DIR::SMARTUNIONS, "SmartUnions" },
249 { ALTIUM_PCB_DIR::TEXTS6, "Texts6" },
250 { ALTIUM_PCB_DIR::TRACKS6, "Tracks6" },
251 { ALTIUM_PCB_DIR::UNIONNAMES, "UnionNames" },
252 { ALTIUM_PCB_DIR::VIAS6, "Vias6" },
253 { ALTIUM_PCB_DIR::WIDESTRINGS6, "WideStrings6" }
254 };
255 // clang-format on
256
257 ALTIUM_PCB_COMPOUND_FILE altiumPcbFile( aFileName );
258
259 try
260 {
261 // Parse File
263 pcb.Parse( altiumPcbFile, mapping, m_props );
264 }
265 catch( CFB::CFBException& exception )
266 {
267 THROW_IO_ERROR( exception.what() );
268 }
269
270 if( m_props && m_props->count( "project_file" ) )
271 {
272 const wxString& projectFile = m_props->at( "project_file" );
273
274 auto variants = ParseAltiumProjectVariants( projectFile );
275
276 if( !variants.empty() )
278
280 ParseAltiumProjectParameters( projectFile ) );
281 }
282}
283
284
285long long PCB_IO_ALTIUM_DESIGNER::GetLibraryTimestamp( const wxString& aLibraryPath ) const
286{
287 // File hasn't been loaded yet.
288 if( aLibraryPath.IsEmpty() )
289 return 0;
290
291 wxFileName fn( aLibraryPath );
292
293 if( fn.IsFileReadable() && fn.GetModificationTime().IsValid() )
294 return fn.GetModificationTime().GetValue().GetValue();
295 else
296 return 0;
297}
298
299
300void PCB_IO_ALTIUM_DESIGNER::loadAltiumLibrary( const wxString& aLibraryPath )
301{
302 // Suppress font substitution warnings (RAII - automatically restored on scope exit)
303 FONTCONFIG_REPORTER_SCOPE fontconfigScope( nullptr );
304
305 long long timestamp = GetLibraryTimestamp( aLibraryPath );
306
307 if( m_fplibFiles.contains( aLibraryPath ) && m_fplibFiles[aLibraryPath].m_Timestamp == timestamp )
308 return; // Already loaded
309
310 try
311 {
312 ALTIUM_FILE_CACHE& libFiles = m_fplibFiles[aLibraryPath];
313
314 if( aLibraryPath.Lower().EndsWith( wxS( ".pcblib" ) ) )
315 {
316 libFiles.m_Files.emplace_back( std::make_unique<ALTIUM_PCB_COMPOUND_FILE>( aLibraryPath ) );
317 }
318 else if( aLibraryPath.Lower().EndsWith( wxS( ".intlib" ) ) )
319 {
320 std::unique_ptr<ALTIUM_PCB_COMPOUND_FILE> lib = std::make_unique<ALTIUM_PCB_COMPOUND_FILE>( aLibraryPath );
321
322 for( const auto& [pcbLibName, pcbCfe] : lib->EnumDir( L"PCBLib" ) )
323 {
324 std::unique_ptr<ALTIUM_PCB_COMPOUND_FILE> libFile = std::make_unique<ALTIUM_PCB_COMPOUND_FILE>();
325
326 if( lib->DecodeIntLibStream( *pcbCfe, libFile.get() ) )
327 libFiles.m_Files.emplace_back( std::move( libFile ) );
328 }
329 }
330
331 libFiles.m_Timestamp = timestamp;
332 }
333 catch( CFB::CFBException& exception )
334 {
335 THROW_IO_ERROR( exception.what() );
336 }
337}
338
339
340void PCB_IO_ALTIUM_DESIGNER::FootprintEnumerate( wxArrayString& aFootprintNames,
341 const wxString& aLibraryPath, bool aBestEfforts,
342 const std::map<std::string, UTF8>* aProperties )
343{
344 loadAltiumLibrary( aLibraryPath );
345
346 if( !m_fplibFiles.contains( aLibraryPath ) )
347 return; // No footprint libraries in file, ignore.
348
349 try
350 {
351 for( std::unique_ptr<ALTIUM_PCB_COMPOUND_FILE>& altiumLibFile : m_fplibFiles[aLibraryPath].m_Files )
352 {
353 // Map code-page-dependent names to unicode names
354 CASE_INSENSITIVE_MAP<wxString> patternMap = altiumLibFile->ListLibFootprints();
355
356 const std::vector<std::string> streamName = { "Library", "Data" };
357 const CFB::COMPOUND_FILE_ENTRY* libraryData = altiumLibFile->FindStream( streamName );
358
359 if( libraryData == nullptr )
360 THROW_IO_ERRORF( _( "File not found: '%s'." ), FormatPath( streamName ) );
361
362 ALTIUM_BINARY_PARSER parser( *altiumLibFile, libraryData );
363
364 std::map<wxString, wxString> properties = parser.ReadProperties();
365
366 uint32_t numberOfFootprints = parser.Read<uint32_t>();
367
368 // The number of footprints might be bogus. Following it is a list of index strings to
369 // find the footprints, and this list might actually be longer than how many footprints
370 // actually are in the library.
371 // If this case was detected, truncate the list to the number of footprints.
372 // Sample in #18452 shows that by simply truncating can get the correct footprints.
373 // Fixes https://gitlab.com/kicad/code/kicad/issues/18452.
374 // After truncation we shall no longer detect if the stream is fully parsed.
375 bool footprintListNotTruncated = true;
376
377 if ( patternMap.size() < numberOfFootprints )
378 {
379 numberOfFootprints = patternMap.size();
380 footprintListNotTruncated = false;
381 }
382
383 aFootprintNames.Alloc( numberOfFootprints );
384
385 for( size_t i = 0; i < numberOfFootprints; i++ )
386 {
388
389 wxScopedCharBuffer charBuffer = parser.ReadCharBuffer();
390 wxString fpPattern( charBuffer, wxConvISO8859_1 );
391
392 auto it = patternMap.find( fpPattern );
393
394 if( it != patternMap.end() )
395 aFootprintNames.Add( it->second ); // Proper unicode name
396 else
397 THROW_IO_ERRORF( _( "Component name not found: '%s'" ), fpPattern );
398
399 parser.SkipSubrecord();
400 }
401
402 if( parser.HasParsingError() )
403 THROW_IO_ERRORF( wxT( "%s stream was not parsed correctly" ), FormatPath( streamName ) );
404
405 if( footprintListNotTruncated && parser.GetRemainingBytes() != 0 )
406 THROW_IO_ERRORF( wxT( "%s stream is not fully parsed" ), FormatPath( streamName ) );
407 }
408 }
409 catch( CFB::CFBException& exception )
410 {
411 THROW_IO_ERROR( exception.what() );
412 }
413}
414
415
416std::unique_ptr<FOOTPRINT> PCB_IO_ALTIUM_DESIGNER::FootprintLoad( const wxString& aLibraryPath,
417 const wxString& aFootprintName, bool aKeepUUID,
418 const std::map<std::string, UTF8>* aProperties )
419{
420 loadAltiumLibrary( aLibraryPath );
421
422 if( !m_fplibFiles.contains( aLibraryPath ) )
423 THROW_IO_ERRORF( _( "No footprints in library '%s'" ), aLibraryPath );
424
425 try
426 {
427 for( std::unique_ptr<ALTIUM_PCB_COMPOUND_FILE>& altiumLibFile : m_fplibFiles[aLibraryPath].m_Files )
428 {
429 altiumLibFile->CacheLibModels();
430 auto [dirName, fpCfe] = altiumLibFile->FindLibFootprintDirName( aFootprintName );
431
432 if( dirName.IsEmpty() )
433 continue;
434
435 // Parse File
436 ALTIUM_PCB pcb( m_board, nullptr, m_layer_mapping_handler, m_reporter, aLibraryPath, aFootprintName );
437 return pcb.ParseFootprint( *altiumLibFile, aFootprintName );
438 }
439 }
440 catch( CFB::CFBException& exception )
441 {
442 THROW_IO_ERROR( exception.what() );
443 }
444
445 THROW_IO_ERRORF( _( "Footprint '%s' not found in '%s'." ), aFootprintName, aLibraryPath );
446}
447
448
450{
451 std::vector<FOOTPRINT*> footprints;
452
453 // caller owns result, clone not alias
454 for( FOOTPRINT* fp : m_board->Footprints() )
455 footprints.push_back( static_cast<FOOTPRINT*>( fp->Clone() ) );
456
457 return footprints;
458}
const char * name
std::string FormatPath(const std::vector< std::string > &aVectorPath)
Helper for debug logging (vector -> string)
@ EXTENDPRIMITIVEINFORMATION
Definition altium_pcb.h:56
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 >();})
std::unique_ptr< 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, const std::map< std::string, UTF8 > *aProperties=nullptr)
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
void AddVariant(const wxString &aVariantName)
Definition board.cpp:3184
const FOOTPRINTS & Footprints() const
Definition board.h:463
void SetVariantDescription(const wxString &aVariantName, const wxString &aDescription)
Definition board.cpp:3325
RAII class to set and restore the fontconfig reporter.
Definition reporter.h:385
Variant information for a footprint.
Definition footprint.h:227
void SetExcludedFromPosFiles(bool aExclude)
Definition footprint.h:251
void SetDNP(bool aDNP)
Definition footprint.h:242
void SetFieldValue(const wxString &aFieldName, const wxString &aValue)
Set a field value override for this variant.
Definition footprint.h:273
void SetExcludedFromBOM(bool aExclude)
Definition footprint.h:245
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:351
long long GetLibraryTimestamp(const wxString &aLibraryPath) const override
Generate a timestamp representing all the files in the library (including the library directory).
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.
std::unique_ptr< 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 loadAltiumLibrary(const wxString &aLibraryPath)
void loadBoard(const wxString &aFileName, BOARD &aBoard, bool aIsNewLoad, const std::map< std::string, UTF8 > *aProperties, PROJECT *aProject=nullptr) override
Parse aFileName into aBoard.
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
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:368
virtual bool CanReadBoard(const wxString &aFileName) const
Checks if this PCB_IO can read the specified board file.
Definition pcb_io.cpp:40
PCB_IO(const wxString &aName)
Definition pcb_io.h:351
const std::map< std::string, UTF8 > * m_props
Properties passed via Save() or Load(), no ownership, may be NULL.
Definition pcb_io.h:371
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:30
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:60
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