7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-21 00:21:25 +00:00

Fix Eagle schematic import issue.

Handle duplicate symbol library names with Eagle URN notation.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/19666
This commit is contained in:
Wayne Stambaugh 2025-01-19 18:41:05 -05:00
parent 5e6795a774
commit db877e6a00
4 changed files with 175 additions and 44 deletions
common/io/eagle
eeschema/sch_io/eagle
pcbnew/pcb_io/eagle

View File

@ -35,6 +35,7 @@
#include <trace_helpers.h>
#include <wx/log.h>
#include <wx/regex.h>
#include <wx/tokenzr.h>
#include <functional>
#include <cstdio>
@ -213,8 +214,6 @@ size_t GetNodeCount( const wxXmlNode* aNode )
return cnt;
}
template<> template<>
OPTIONAL_XML_ATTRIBUTE<wxString>::OPTIONAL_XML_ATTRIBUTE( wxString aData )
{
@ -291,6 +290,55 @@ long long int ECOORD::ConvertToNm( int aValue, enum EAGLE_UNIT aUnit )
}
EURN::EURN( const wxString& aUrn )
{
Parse( aUrn );
}
void EURN::Parse( const wxString& aUrn )
{
wxStringTokenizer tokens( aUrn, ":" );
host = tokens.GetNextToken();
path = tokens.GetNextToken();
assetType = tokens.GetNextToken();
// Split off the version if there is one.
wxString tmp = tokens.GetNextToken();
assetId = tmp.BeforeFirst( '/' );
assetVersion = tmp.AfterLast( '/' );
}
bool EURN::IsValid() const
{
if( host != "urn" )
return false;
if( path.IsEmpty() )
return false;
static std::set<wxString> validAssetTypes =
{
"component",
"footprint",
"library",
"package",
"symbol"
};
if( validAssetTypes.count( assetType ) == 0 )
return false;
if( assetId.IsEmpty() )
return false;
return true;
}
// Template specializations below parse wxString to the used types:
// - wxString (preferred)
// - string
@ -376,6 +424,13 @@ ECOORD Convert<ECOORD>( const wxString& aCoord )
}
template<>
EURN Convert<EURN>( const wxString& aUrn )
{
return EURN( aUrn );
}
/**
* Parse \a aAttribute of the XML node \a aNode.
*
@ -1406,7 +1461,7 @@ EELEMENT::EELEMENT( wxXmlNode* aElement, IO_BASE* aIo ) :
y = parseRequiredAttribute<ECOORD>( aElement, "y" );
// optional
library_urn = parseOptionalAttribute<wxString>( aElement, "library_urn" );
library_urn = parseOptionalAttribute<EURN>( aElement, "library_urn" );
locked = parseOptionalAttribute<bool>( aElement, "locked" );
smashed = parseOptionalAttribute<bool>( aElement, "smashed" );
rot = parseOptionalAttribute<EROT>( aElement, "rot" );
@ -1471,7 +1526,7 @@ EPART::EPART( wxXmlNode* aPart, IO_BASE* aIo ) :
*/
name = parseRequiredAttribute<wxString>( aPart, "name" );
library = parseRequiredAttribute<wxString>( aPart, "library" );
libraryUrn = parseOptionalAttribute<wxString>( aPart, "library_urn" );
libraryUrn = parseOptionalAttribute<EURN>( aPart, "library_urn" );
deviceset = parseRequiredAttribute<wxString>( aPart, "deviceset" );
device = parseRequiredAttribute<wxString>( aPart, "device" );
package3d_urn = parseOptionalAttribute<wxString>( aPart, "package3d_urn" );
@ -1717,7 +1772,7 @@ EDEVICE_SET::EDEVICE_SET( wxXmlNode* aDeviceSet, IO_BASE* aIo ) :
* inside boards or schematics -->
*/
name = parseRequiredAttribute<wxString>( aDeviceSet, "name" );
urn = parseOptionalAttribute<wxString>( aDeviceSet, "urn" );
urn = parseOptionalAttribute<EURN>( aDeviceSet, "urn" );
locally_modified = parseOptionalAttribute<bool>( aDeviceSet, "locally_modified" );
prefix = parseOptionalAttribute<wxString>( aDeviceSet, "prefix" );
uservalue = parseOptionalAttribute<bool>( aDeviceSet, "uservalue" );
@ -2216,7 +2271,7 @@ EPACKAGE::EPACKAGE( wxXmlNode* aPackage, IO_BASE* aIo ) :
* inside boards or schematics -->
*/
name = parseRequiredAttribute<wxString>( aPackage, "name" );
urn = parseOptionalAttribute<wxString>( aPackage, "urn" );
urn = parseOptionalAttribute<EURN>( aPackage, "urn" );
locally_modified = parseOptionalAttribute<bool>( aPackage, "locally_modified" );
library_version = parseOptionalAttribute<int>( aPackage, "library_version" );
library_locally_modified = parseOptionalAttribute<bool>( aPackage, "library_locally_modified" );
@ -2349,7 +2404,7 @@ ESYMBOL::ESYMBOL( wxXmlNode* aSymbol, IO_BASE* aIo ) :
*/
name = parseRequiredAttribute<wxString>( aSymbol, "name" );
urn = parseOptionalAttribute<wxString>( aSymbol, "urn" );
urn = parseOptionalAttribute<EURN>( aSymbol, "urn" );
locally_modified = parseOptionalAttribute<bool>( aSymbol, "locally_modified" );
library_version = parseOptionalAttribute<int>( aSymbol, "library_version" );
library_locally_modified = parseOptionalAttribute<bool>( aSymbol, "library_locally_modified" );
@ -2420,7 +2475,7 @@ ELIBRARY::ELIBRARY( wxXmlNode* aLibrary, IO_BASE* aIo ) :
if( parentNodeName == "libraries" )
{
name = parseRequiredAttribute<wxString>( aLibrary, "name" );
urn = parseOptionalAttribute<wxString>( aLibrary, "urn" );
urn = parseOptionalAttribute<EURN>( aLibrary, "urn" );
}
for( wxXmlNode* child = aLibrary->GetChildren(); child; child = child->GetNext() )
@ -2491,6 +2546,24 @@ ELIBRARY::ELIBRARY( wxXmlNode* aLibrary, IO_BASE* aIo ) :
}
wxString ELIBRARY::GetName() const
{
wxString libName = name;
// Use the name when no library urn exists.
if( !urn )
return libName;
// Suffix the library name with the urn library identifier. Eagle schematics can have
// mulitple libraries with the same name. The urn library identifier is used to prevent
// library name clashes.
if( urn->IsValid() )
libName += wxS( "_" ) + urn->assetId;
return libName;
}
EAPPROVED::EAPPROVED( wxXmlNode* aApproved, IO_BASE* aIo ) :
EAGLE_BASE( aIo )
{
@ -2533,7 +2606,34 @@ ESCHEMATIC::ESCHEMATIC( wxXmlNode* aSchematic, IO_BASE* aIo ) :
if( library->GetName() == "library" )
{
std::unique_ptr<ELIBRARY> tmp = std::make_unique<ELIBRARY>( library, aIo );
libraries[ tmp->name ] = std::move( tmp );
wxString libName = tmp->GetName();
// Prevent duplicate library names. This should only happen if the Eagle
// file has an invalid format.
if( libraries.find( libName ) != libraries.end() )
{
wxString uniqueName;
std::set<wxString> usedNames;
for( const auto& [setName, setLibrary] : libraries )
usedNames.emplace( setName );
if( usedNames.find( libName ) != usedNames.end() )
{
int i = 1;
do
{
uniqueName.Format( wxS( "%s_%d" ), libName, i );
i += 1;
} while( usedNames.find( uniqueName ) != usedNames.end() );
}
libName = uniqueName;
}
libraries[ libName ] = std::move( tmp );
}
}

View File

@ -386,13 +386,15 @@ VECTOR2I ConvertArcCenter( const VECTOR2I& aStart, const VECTOR2I& aEnd, double
// Pre-declare for typedefs
struct EROT;
struct ECOORD;
struct EURN;
typedef OPTIONAL_XML_ATTRIBUTE<wxString> opt_wxString;
typedef OPTIONAL_XML_ATTRIBUTE<int> opt_int;
typedef OPTIONAL_XML_ATTRIBUTE<double> opt_double;
typedef OPTIONAL_XML_ATTRIBUTE<bool> opt_bool;
typedef OPTIONAL_XML_ATTRIBUTE<EROT> opt_erot;
typedef OPTIONAL_XML_ATTRIBUTE<ECOORD> opt_ecoord;
typedef OPTIONAL_XML_ATTRIBUTE<int> opt_int;
typedef OPTIONAL_XML_ATTRIBUTE<double> opt_double;
typedef OPTIONAL_XML_ATTRIBUTE<bool> opt_bool;
typedef OPTIONAL_XML_ATTRIBUTE<EROT> opt_erot;
typedef OPTIONAL_XML_ATTRIBUTE<ECOORD> opt_ecoord;
typedef OPTIONAL_XML_ATTRIBUTE<EURN> opt_eurn;
struct EAGLE_BASE
@ -413,6 +415,45 @@ struct EAGLE_BASE
};
/**
* Container that parses Eagle library file "urn" definitions.
*
* According to the eagle.dtd, the "urn" definition is as follows:
*
* <!ENTITY % Urn "%String;"> <!-- of the form "urn:adsk.eagle:<ASSET_TYPE>:<ASSET_ID>/<VERSION>"
* - <ASSET_TYPE> is "symbol", "footprint", "package", "component", or "library"
* - <ASSET_ID> is an integer
* - <VERSION> is an integer
*
* The "/<VERSION>" is omitted when referencing the asset without specifying a particular version.
* For example, "urn:adsk.eagle:component:60986/2" references version 2 of component 60986 and
* "urn:adsk.eagle:library:60968" references library 60986 without specifying a version.
*/
struct EURN : public EAGLE_BASE
{
EURN() {}
/// Parse an Eagle "urn" string.
EURN( const wxString& aUrn );
void Parse( const wxString& aUrn );
/**
* Check if the string passed to the ctor was a valid Eagle urn.
*
* @retval true if the urn string is valid.
* @retval false if the urn string is not valid.
*/
bool IsValid() const;
wxString host; ///< Should always be "urn".
wxString path; ///< Path to the asset type below.
wxString assetType; ///< Must be "symbol", "footprint", "package", "component", or "library".
wxString assetId; ///< The unique asset identifier for the asset type.
wxString assetVersion; ///< May be empty depending on the asset type.
};
// All of the 'E'STRUCTS below merely hold Eagle XML information verbatim, in binary.
// For maintenance and troubleshooting purposes, it was thought that we'd need to
// separate the conversion process into distinct steps. There is no intent to have KiCad
@ -1282,7 +1323,7 @@ struct EELEMENT : public EAGLE_BASE
wxString name;
wxString library;
opt_wxString library_urn;
opt_eurn library_urn;
wxString package;
opt_wxString package3d_urn;
opt_wxString override_package3d_urn;
@ -1455,7 +1496,7 @@ struct EPART : public EAGLE_BASE
wxString name;
wxString library;
opt_wxString libraryUrn;
opt_eurn libraryUrn;
wxString deviceset;
wxString device;
opt_wxString package3d_urn;
@ -1588,7 +1629,7 @@ struct EDEVICE_SET : public EAGLE_BASE
*/
wxString name;
opt_wxString urn;
opt_eurn urn;
opt_bool locally_modified;
opt_wxString prefix;
opt_bool uservalue;
@ -2005,7 +2046,7 @@ struct EPACKAGE : public EAGLE_BASE
* inside boards or schematics -->
*/
wxString name;
opt_wxString urn;
opt_eurn urn;
opt_bool locally_modified;
opt_int library_version;
opt_bool library_locally_modified;
@ -2055,7 +2096,7 @@ struct EPACKAGE3D : public EAGLE_BASE
* inside boards or schematics -->
*/
wxString name;
wxString urn;
EURN urn;
wxString type;
opt_int library_version;
opt_bool library_locally_modified;
@ -2084,7 +2125,7 @@ struct ESYMBOL : public EAGLE_BASE
*/
wxString name;
opt_wxString urn;
opt_eurn urn;
opt_bool locally_modified;
opt_int library_version;
opt_bool library_locally_modified;
@ -2115,7 +2156,7 @@ struct ELIBRARY : public EAGLE_BASE
* <!-- urn: Only in online libraries used inside boards or schematics -->
*/
wxString name;
opt_wxString urn;
opt_eurn urn;
std::optional<EDESCRIPTION> description;
std::map<wxString, std::unique_ptr<EPACKAGE>> packages;
@ -2123,6 +2164,12 @@ struct ELIBRARY : public EAGLE_BASE
std::map<wxString, std::unique_ptr<ESYMBOL>> symbols;
std::map<wxString, std::unique_ptr<EDEVICE_SET>> devicesets;
/**
* Fetch the fully unique library name.
*
* @return the unique library name.
*/
wxString GetName() const;
ELIBRARY( wxXmlNode* aLibrary, IO_BASE* aIo = nullptr );
};

View File

@ -676,19 +676,10 @@ void SCH_IO_EAGLE::loadSchematic( const ESCHEMATIC& aSchematic )
{
for( const auto& [name, elibrary] : aSchematic.libraries )
{
wxString libName = elibrary->name;
EAGLE_LIBRARY* elib = &m_eagleLibs[elibrary->GetName()];
elib->name = elibrary->GetName();
if( elibrary->urn )
{
wxString tmp = *elibrary->urn;
libName += tmp.AfterLast( '/' );
}
EAGLE_LIBRARY* elib = &m_eagleLibs[libName];
elib->name = libName;
loadLibrary( elibrary.get(), &m_eagleLibs[libName] );
loadLibrary( elibrary.get(), &m_eagleLibs[elibrary->GetName()] );
}
m_pi->SaveLibrary( getLibFileName().GetFullPath() );
@ -1740,11 +1731,7 @@ void SCH_IO_EAGLE::loadInstance( const std::unique_ptr<EINSTANCE>& aInstance,
// Correctly handle versioned libraries.
if( epart->libraryUrn )
{
wxString tmp = *epart->libraryUrn;
libName += tmp.AfterLast( '/' );
}
libName += wxS( "_" ) + epart->libraryUrn->assetId;
wxString gatename = epart->deviceset + wxS( "_" ) + epart->device + wxS( "_" ) +
aInstance->gate;
@ -1766,7 +1753,7 @@ void SCH_IO_EAGLE::loadInstance( const std::unique_ptr<EINSTANCE>& aInstance,
if( libIt == m_eagleLibs.end() )
{
Report( wxString::Format( wxS( "Eagle library '%s' not found while looking up symbol for"
Report( wxString::Format( wxS( "Eagle library '%s' not found while looking up symbol for "
"deviceset '%s', device '%s', and gate '%s." ),
libName, epart->deviceset, epart->device, aInstance->gate ) );
return;
@ -2052,7 +2039,7 @@ EAGLE_LIBRARY* SCH_IO_EAGLE::loadLibrary( const ELIBRARY* aLibrary, EAGLE_LIBRAR
if( it == aLibrary->symbols.end() )
{
Report( wxString::Format( wxS( "Eagle symbol '%s' not found in library '%s'." ),
egate->symbol, aLibrary->name ) );
egate->symbol, aLibrary->GetName() ) );
continue;
}

View File

@ -1272,10 +1272,7 @@ void PCB_IO_EAGLE::loadElements( wxXmlNode* aElements )
wxString packageName = e.package;
if( e.library_urn )
{
wxString libOrdinal = *e.library_urn;
packageName = e.package + wxS( "_" ) + libOrdinal.AfterLast( ':' );
}
packageName = e.package + wxS( "_" ) + e.library_urn->assetId;
wxString pkg_key = makeKey( e.library, packageName );
auto it = m_templates.find( pkg_key );