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
175
176
180
181
183 const std::vector<INPUT_LAYER_DESC>& aInputLayerDescriptionVector )
184{
185 std::map<wxString, PCB_LAYER_ID> retval;
186
187 // Just return a the auto-mapped layers
188 for( INPUT_LAYER_DESC layerDesc : aInputLayerDescriptionVector )
189 {
190 retval.insert( { layerDesc.Name, layerDesc.AutoMapLayer } );
191 }
192
193 return retval;
194}
195
196
197bool PCB_IO_ALTIUM_DESIGNER::checkFileHeader( const wxString& aFileName )
198{
199 // Compound File Binary Format header
201}
202
203
204bool PCB_IO_ALTIUM_DESIGNER::CanReadBoard( const wxString& aFileName ) const
205{
206 if( !PCB_IO::CanReadBoard( aFileName ) )
207 return false;
208
209 return checkFileHeader( aFileName );
210}
211
212
213bool PCB_IO_ALTIUM_DESIGNER::CanReadLibrary( const wxString& aFileName ) const
214{
215 if( !PCB_IO::CanReadLibrary( aFileName ) )
216 return false;
217
218 return checkFileHeader( aFileName );
219}
220
221
222BOARD* PCB_IO_ALTIUM_DESIGNER::LoadBoard( const wxString& aFileName, BOARD* aAppendToMe,
223 const std::map<std::string, UTF8>* aProperties, PROJECT* aProject )
224{
225
226 m_props = aProperties;
227
228 m_board = aAppendToMe ? aAppendToMe : new BOARD();
229
230 // Collect the font substitution warnings (RAII - automatically reset on scope exit)
232
233 // Give the filename to the board if it's new
234 if( !aAppendToMe )
235 m_board->SetFileName( aFileName );
236
237 // clang-format off
238 const std::map<ALTIUM_PCB_DIR, std::string> mapping = {
239 { ALTIUM_PCB_DIR::FILE_HEADER, "FileHeader" },
240 { ALTIUM_PCB_DIR::ARCS6, "Arcs6" },
241 { ALTIUM_PCB_DIR::BOARD6, "Board6" },
242 { ALTIUM_PCB_DIR::BOARDREGIONS, "BoardRegions" },
243 { ALTIUM_PCB_DIR::CLASSES6, "Classes6" },
244 { ALTIUM_PCB_DIR::COMPONENTS6, "Components6" },
245 { ALTIUM_PCB_DIR::COMPONENTBODIES6, "ComponentBodies6" },
246 { ALTIUM_PCB_DIR::DIMENSIONS6, "Dimensions6" },
247 { ALTIUM_PCB_DIR::EXTENDPRIMITIVEINFORMATION, "ExtendedPrimitiveInformation" },
248 { ALTIUM_PCB_DIR::FILLS6, "Fills6" },
249 { ALTIUM_PCB_DIR::MODELS, "Models" },
250 { ALTIUM_PCB_DIR::NETS6, "Nets6" },
251 { ALTIUM_PCB_DIR::PADS6, "Pads6" },
252 { ALTIUM_PCB_DIR::POLYGONS6, "Polygons6" },
253 { ALTIUM_PCB_DIR::REGIONS6, "Regions6" },
254 { ALTIUM_PCB_DIR::RULES6, "Rules6" },
255 { ALTIUM_PCB_DIR::SHAPEBASEDREGIONS6, "ShapeBasedRegions6" },
256 { ALTIUM_PCB_DIR::SMARTUNIONS, "SmartUnions" },
257 { ALTIUM_PCB_DIR::TEXTS6, "Texts6" },
258 { ALTIUM_PCB_DIR::TRACKS6, "Tracks6" },
259 { ALTIUM_PCB_DIR::VIAS6, "Vias6" },
260 { ALTIUM_PCB_DIR::WIDESTRINGS6, "WideStrings6" }
261 };
262 // clang-format on
263
264 ALTIUM_PCB_COMPOUND_FILE altiumPcbFile( aFileName );
265
266 try
267 {
268 // Parse File
270 pcb.Parse( altiumPcbFile, mapping );
271 }
272 catch( CFB::CFBException& exception )
273 {
274 THROW_IO_ERROR( exception.what() );
275 }
276
277 if( m_props && m_props->count( "project_file" ) )
278 {
279 const wxString& projectFile = m_props->at( "project_file" );
280
281 auto variants = ParseAltiumProjectVariants( projectFile );
282
283 if( !variants.empty() )
285
287 ParseAltiumProjectParameters( projectFile ) );
288 }
289
290 return m_board;
291}
292
293
294long long PCB_IO_ALTIUM_DESIGNER::GetLibraryTimestamp( const wxString& aLibraryPath ) const
295{
296 // File hasn't been loaded yet.
297 if( aLibraryPath.IsEmpty() )
298 return 0;
299
300 wxFileName fn( aLibraryPath );
301
302 if( fn.IsFileReadable() && fn.GetModificationTime().IsValid() )
303 return fn.GetModificationTime().GetValue().GetValue();
304 else
305 return 0;
306}
307
308
309void PCB_IO_ALTIUM_DESIGNER::loadAltiumLibrary( const wxString& aLibraryPath )
310{
311 // Suppress font substitution warnings (RAII - automatically restored on scope exit)
312 FONTCONFIG_REPORTER_SCOPE fontconfigScope( nullptr );
313
314 long long timestamp = GetLibraryTimestamp( aLibraryPath );
315
316 if( m_fplibFiles.contains( aLibraryPath ) && m_fplibFiles[aLibraryPath].m_Timestamp == timestamp )
317 return; // Already loaded
318
319 try
320 {
321 ALTIUM_FILE_CACHE& libFiles = m_fplibFiles[aLibraryPath];
322
323 if( aLibraryPath.Lower().EndsWith( wxS( ".pcblib" ) ) )
324 {
325 libFiles.m_Files.emplace_back( std::make_unique<ALTIUM_PCB_COMPOUND_FILE>( aLibraryPath ) );
326 }
327 else if( aLibraryPath.Lower().EndsWith( wxS( ".intlib" ) ) )
328 {
329 std::unique_ptr<ALTIUM_PCB_COMPOUND_FILE> lib = std::make_unique<ALTIUM_PCB_COMPOUND_FILE>( aLibraryPath );
330
331 for( const auto& [pcbLibName, pcbCfe] : lib->EnumDir( L"PCBLib" ) )
332 {
333 std::unique_ptr<ALTIUM_PCB_COMPOUND_FILE> libFile = std::make_unique<ALTIUM_PCB_COMPOUND_FILE>();
334
335 if( lib->DecodeIntLibStream( *pcbCfe, libFile.get() ) )
336 libFiles.m_Files.emplace_back( std::move( libFile ) );
337 }
338 }
339
340 libFiles.m_Timestamp = timestamp;
341 }
342 catch( CFB::CFBException& exception )
343 {
344 THROW_IO_ERROR( exception.what() );
345 }
346}
347
348
349void PCB_IO_ALTIUM_DESIGNER::FootprintEnumerate( wxArrayString& aFootprintNames,
350 const wxString& aLibraryPath, bool aBestEfforts,
351 const std::map<std::string, UTF8>* aProperties )
352{
353 loadAltiumLibrary( aLibraryPath );
354
355 if( !m_fplibFiles.contains( aLibraryPath ) )
356 return; // No footprint libraries in file, ignore.
357
358 try
359 {
360 for( std::unique_ptr<ALTIUM_PCB_COMPOUND_FILE>& altiumLibFile : m_fplibFiles[aLibraryPath].m_Files )
361 {
362 // Map code-page-dependent names to unicode names
363 CASE_INSENSITIVE_MAP<wxString> patternMap = altiumLibFile->ListLibFootprints();
364
365 const std::vector<std::string> streamName = { "Library", "Data" };
366 const CFB::COMPOUND_FILE_ENTRY* libraryData = altiumLibFile->FindStream( streamName );
367
368 if( libraryData == nullptr )
369 {
370 THROW_IO_ERROR( wxString::Format( _( "File not found: '%s'." ),
371 FormatPath( streamName ) ) );
372 }
373
374 ALTIUM_BINARY_PARSER parser( *altiumLibFile, libraryData );
375
376 std::map<wxString, wxString> properties = parser.ReadProperties();
377
378 uint32_t numberOfFootprints = parser.Read<uint32_t>();
379
380 // The number of footprints might be bogus. Following it is a list of index strings to
381 // find the footprints, and this list might actually be longer than how many footprints
382 // actually are in the library.
383 // If this case was detected, truncate the list to the number of footprints.
384 // Sample in #18452 shows that by simply truncating can get the correct footprints.
385 // Fixes https://gitlab.com/kicad/code/kicad/issues/18452.
386 // After truncation we shall no longer detect if the stream is fully parsed.
387 bool footprintListNotTruncated = true;
388
389 if ( patternMap.size() < numberOfFootprints )
390 {
391 numberOfFootprints = patternMap.size();
392 footprintListNotTruncated = false;
393 }
394
395 aFootprintNames.Alloc( numberOfFootprints );
396
397 for( size_t i = 0; i < numberOfFootprints; i++ )
398 {
400
401 wxScopedCharBuffer charBuffer = parser.ReadCharBuffer();
402 wxString fpPattern( charBuffer, wxConvISO8859_1 );
403
404 auto it = patternMap.find( fpPattern );
405
406 if( it != patternMap.end() )
407 {
408 aFootprintNames.Add( it->second ); // Proper unicode name
409 }
410 else
411 {
412 THROW_IO_ERROR( wxString::Format( "Component name not found: '%s'", fpPattern ) );
413 }
414
415 parser.SkipSubrecord();
416 }
417
418 if( parser.HasParsingError() )
419 {
420 THROW_IO_ERROR( wxString::Format( "%s stream was not parsed correctly",
421 FormatPath( streamName ) ) );
422 }
423
424 if( footprintListNotTruncated && parser.GetRemainingBytes() != 0 )
425 {
426 THROW_IO_ERROR( wxString::Format( "%s stream is not fully parsed",
427 FormatPath( streamName ) ) );
428 }
429 }
430 }
431 catch( CFB::CFBException& exception )
432 {
433 THROW_IO_ERROR( exception.what() );
434 }
435}
436
437
439 const wxString& aFootprintName, bool aKeepUUID,
440 const std::map<std::string, UTF8>* aProperties )
441{
442 loadAltiumLibrary( aLibraryPath );
443
444 if( !m_fplibFiles.contains( aLibraryPath ) )
445 THROW_IO_ERROR( wxString::Format( _( "No footprints in library '%s'" ), aLibraryPath ) );
446
447 try
448 {
449 for( std::unique_ptr<ALTIUM_PCB_COMPOUND_FILE>& altiumLibFile : m_fplibFiles[aLibraryPath].m_Files )
450 {
451 altiumLibFile->CacheLibModels();
452 auto [dirName, fpCfe] = altiumLibFile->FindLibFootprintDirName( aFootprintName );
453
454 if( dirName.IsEmpty() )
455 continue;
456
457 // Parse File
458 ALTIUM_PCB pcb( m_board, nullptr, m_layer_mapping_handler, m_reporter, aLibraryPath, aFootprintName );
459 return pcb.ParseFootprint( *altiumLibFile, aFootprintName );
460 }
461 }
462 catch( CFB::CFBException& exception )
463 {
464 THROW_IO_ERROR( exception.what() );
465 }
466
467 THROW_IO_ERROR( wxString::Format( _( "Footprint '%s' not found in '%s'." ),
468 aFootprintName,
469 aLibraryPath ) );
470}
471
472
474{
475 std::vector<FOOTPRINT*> footprints;
476
477 for( FOOTPRINT* fp : m_board->Footprints() )
478 footprints.push_back( fp );
479
480 return footprints;
481}
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:372
void AddVariant(const wxString &aVariantName)
Definition board.cpp:2861
const FOOTPRINTS & Footprints() const
Definition board.h:420
void SetVariantDescription(const wxString &aVariantName, const wxString &aDescription)
Definition board.cpp:2967
RAII class to set and restore the fontconfig reporter.
Definition reporter.h:332
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:237
PROGRESS_REPORTER * m_progressReporter
Progress reporter to track the progress of the operation, may be nullptr.
Definition io_base.h:240
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:247
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:62
void IncrementTextVarsTicker()
Definition project.h:111
virtual std::map< wxString, wxString > & GetTextVars() const
Definition project.cpp:126
static REPORTER & GetInstance()
Definition reporter.cpp:220
#define _(s)
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
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