Sure.
We’ve been using a DbLib in Altium for years. The table format is in fact very similar; I just had to create a new table based on the data we already had in there. The reason for that is that KiCad typically uses multiple parts per file for schematic symbols while we were using single part per file in Altium. That’s one of my pet peeves which is better in Altium because it enables nicer version control of parts. That was the easy part.
That’s the schema for the database:
USE [KicadLibrary]
GO
/****** Object: Table [dbo].[Parts] Script Date: 24.06.2025 08:20:45 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Parts](
[Ident] [nvarchar](32) NOT NULL,
[Symbols] [nvarchar](512) NULL,
[Footprints] [nvarchar](512) NULL,
[Description] [nvarchar](512) NULL,
[ValueA] [nvarchar](256) NULL,
[ValueB] [nvarchar](256) NULL,
[CheckedBy] [nvarchar](16) NULL,
[CheckedOn] [datetime] NULL,
[CreatedBy] [nvarchar](16) NULL,
[CreatedOn] [datetime] NULL,
[Datasheet1Uri] [nvarchar](256) NULL,
[Datasheet2Uri] [nvarchar](256) NULL,
[Datasheet3Uri] [nvarchar](256) NULL,
[Manufacturer1] [nvarchar](128) NULL,
[Manufacturer2] [nvarchar](128) NULL,
[Manufacturer3] [nvarchar](128) NULL,
[Manufacturer4] [nvarchar](128) NULL,
[Manufacturer5] [nvarchar](128) NULL,
[ManufacturerPartNumber1] [nvarchar](128) NULL,
[ManufacturerPartNumber2] [nvarchar](128) NULL,
[ManufacturerPartNumber3] [nvarchar](128) NULL,
[ManufacturerPartNumber4] [nvarchar](128) NULL,
[ManufacturerPartNumber5] [nvarchar](128) NULL,
[Package] [nvarchar](128) NULL,
[PackageGroup] [nvarchar](1) NULL,
CONSTRAINT [PK_Parts] PRIMARY KEY CLUSTERED
(
[Ident] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
The symbols and footprints can be converted to KiCad format automatically. For symbols, you will need to use the one file contains many parts methods because you will need to add the files to the library in Kicad (you cannot simply add the kicad_dbl file and expect it to find your symbols - this should be changed in Kicad). For footprints it was easier as it’s the same here, just the folders are named .pretty.
This is the C# code for converting your symbols and footprints to kicad format (we’re using the kicad-cli)
Console.WriteLine("Schematic Symbols");
foreach (var schLibFile in Directory.GetFiles(baseAltiumPathSch, "*.schlib", SearchOption.AllDirectories))
{
FileInfo fi = new FileInfo(schLibFile);
var pathAltium = fi.DirectoryName;
if (pathAltium == null)
{
throw new Exception("Invalid");
}
var pathKicad = pathAltium.Replace(baseAltiumPathSch, baseKicadPathSch);
var filenameKicad = fi.Name.Replace(".schlib", ".kicad_sym", true, CultureInfo.InvariantCulture);
var fullFilename = Path.Combine(pathKicad, filenameKicad);
Directory.CreateDirectory(pathKicad);
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = kicadCliExecutable;
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
var argumentsList = $"sym upgrade --output \"{fullFilename}\" \"{schLibFile}\"";
psi.Arguments = argumentsList;
Process process = new Process();
process.StartInfo = psi;
process.Start();
process.WaitForExit(10000);
if (process.ExitCode != 0)
{
Console.Write($"{process.ExitCode}");
Console.WriteLine(" " + process.StandardOutput.ReadToEnd());
Console.WriteLine(" " + process.StandardError.ReadToEnd());
}
}
From that you build the library file for Kicad:
// Build the sym-lib-table file
SNodeList libList = new SNodeList
{
Items = []
};
SExpression sexKicadSymLibTable = new SExpression("sym_lib_table", libList.Items);
libList.Items.Add(new SExpression("version", 7));
var dbLibEntry = new SNodeList
{
Items = []
};
dbLibEntry.Items.Add(new SExpression("name", "kicad_db"));
dbLibEntry.Items.Add(new SExpression("type", "Database"));
dbLibEntry.Items.Add(new SExpression("uri", "${KICAD8_SYMBOL_DIR}/kicad_db.kicad_dbl"));
dbLibEntry.Items.Add(new SExpression("options", ""));
dbLibEntry.Items.Add(new SExpression("descr", ""));
libList.Items.Add(new SExpression("lib", dbLibEntry.Items));
foreach (var file in Directory.GetFiles(baseKicadPathSch, "*.kicad_sym", SearchOption.TopDirectoryOnly))
{
FileInfo fi = new FileInfo(file);
var innerLib = new SNodeList
{
Items = []
};
innerLib.Items.Add(new SExpression("name", fi.Name.Replace(".kicad_sym", "")));
innerLib.Items.Add(new SExpression("type", "KiCad"));
innerLib.Items.Add(new SExpression("uri", fi.FullName.Replace("/", "\\")));
innerLib.Items.Add(new SExpression("options", ""));
innerLib.Items.Add(new SExpression("descr", ""));
innerLib.Items.Add(new SNodeList()
{
Items = new List<SNodeBase> { new SNodeAtom("hidden") }
});
libList.Items.Add(new SExpression("lib", innerLib.Items));
}
sexKicadSymLibTable.WriteToFile(kicadSymLibTable);
This is the code for the footprints
// Footprints, we need to handle the subfolders differently here
// Console.WriteLine("Footprints");
foreach (var pcbLibFile in Directory.GetFiles(baseAltiumPathPcb, "*.pcblib", SearchOption.AllDirectories))
{
// Get the first directory they are in, cannot go deeper than 1 folder
FileInfo fi = new FileInfo(pcbLibFile);
var filenameKicad = fi.Name.Replace(".pcblib", ".kicad_mod", true, CultureInfo.InvariantCulture);
var directories = pcbLibFile.Split(Path.DirectorySeparatorChar);
var directory = directories[3];
directory += ".pretty";
if (Directory.Exists(temporaryConversionFileName))
{
Directory.Delete(temporaryConversionFileName, true);
}
var folder = Path.Combine(baseKicadPathPcb, directory);
Directory.CreateDirectory(folder);
//var destinationFullFilename = Path.Combine(folder, filenameKicad);
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = kicadCliExecutable;
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
var argumentsList = $"fp upgrade --output \"{temporaryConversionFileName}\" \"{pcbLibFile}\"";
psi.Arguments = argumentsList;
Process process = new Process();
process.StartInfo = psi;
process.Start();
process.WaitForExit(10000);
if (process.ExitCode != 0)
{
Console.Write($"{process.ExitCode}");
Console.WriteLine(" " + process.StandardOutput.ReadToEnd());
Console.WriteLine(" " + process.StandardError.ReadToEnd());
continue;
}
// This has now created a library, but we need the file from inside that library
var innerFileName = Path.Combine(temporaryConversionFileName, filenameKicad);
var destinationFilename = Path.Combine(folder, filenameKicad);
File.Move(innerFileName, destinationFilename);
}
From that, you build the footprint library file for Kicad
SNodeList fbList = new SNodeList
{
Items = []
};
SExpression sexKicadFpLibTable = new SExpression("fp_lib_table", fbList.Items);
foreach (var dir in Directory.GetDirectories(baseKicadPathPcb, "*.pretty", SearchOption.TopDirectoryOnly))
{
DirectoryInfo di = new DirectoryInfo(dir);
var innerLib = new SNodeList
{
Items = []
};
innerLib.Items.Add(new SExpression("name", di.Name.Replace(".pretty", "")));
innerLib.Items.Add(new SExpression("type", "KiCad"));
innerLib.Items.Add(new SExpression("uri", di.FullName.Replace("/", "\\")));
innerLib.Items.Add(new SExpression("options", ""));
innerLib.Items.Add(new SExpression("descr", ""));
//innerLib.Items.Add(new SNodeList()
//{
// Items = new List<SNodeBase> { new SNodeAtom("hidden") }
//});
fbList.Items.Add(new SExpression("lib", innerLib.Items));
}
sexKicadFpLibTable.WriteToFile(kicadFpTable);
And here is the code that creates the table from the parts
List<Part> parts = new List<Part>();
// Build a dictionary which part can be found in which library (schematic symbols)
Dictionary<string, string> symLibLocations = new Dictionary<string, string>();
foreach (var kicadSymLibFile in Directory.GetFiles(@"A:\libs_kicad\SCH", "*.kicad_sym", SearchOption.TopDirectoryOnly))
{
// Load the symbols in that file
SExpression sexSymLibFile = new SExpression();
sexSymLibFile.LoadFromFile(kicadSymLibFile);
string symLibName = new FileInfo(kicadSymLibFile).Name.Replace(".kicad_sym", "");
foreach (var item in sexSymLibFile.Items.Where(d => d is SExpression && ((SExpression)d).Name == "symbol"))
{
var sItem = (SExpression)item;
var value = sItem.GetValue();
symLibLocations.Add(value, symLibName + ":" + value);
}
}
// Build a dictionary which part can be found in which library (footprints)
Dictionary<string, string> fpLibLocations = new Dictionary<string, string>();
foreach (var kicadFpLibDirectory in Directory.GetDirectories(@"A:\libs_kicad\PCB", "*.pretty", SearchOption.TopDirectoryOnly))
{
var libName = new DirectoryInfo(kicadFpLibDirectory).Name.Replace(".pretty", "");
foreach (var file in Directory.GetFiles(kicadFpLibDirectory, "*.kicad_mod", SearchOption.TopDirectoryOnly))
{
FileInfo fi = new FileInfo(file);
var fpName = fi.Name.Replace(".kicad_mod", "");
fpLibLocations.Add(fpName, libName + ":" + fpName);
}
}
foreach (var erpPart in erpArticles.Where(d=>!string.IsNullOrEmpty(d.Value.SchematicReference)))
{
var part = new Part();
part.Ident = erpPart.Key;
part.CreatedBy = erpPart.Value.CreatedBy;
part.CreatedOn = erpPart.Value.CreatedOn;
part.CheckedBy = erpPart.Value.CheckedBy;
part.CheckedOn = erpPart.Value.CheckedOn;
part.Datasheet1Uri = erpPart.Value.Datasheet1Uri;
part.Datasheet2Uri = erpPart.Value.Datasheet2Uri;
part.Datasheet3Uri = erpPart.Value.Datasheet3Uri;
part.Description = erpPart.Value.DescriptionLong;
if (symLibLocations.ContainsKey(erpPart.Value.SchematicReference))
{
part.Symbols = symLibLocations[erpPart.Value.SchematicReference];
}
else
{
Console.WriteLine($"{part.Ident}: Symbol {erpPart.Value.SchematicReference} nicht gefunden");
}
var footprints = new List<string>
{
(erpPart.Value.FootprintReference),
(erpPart.Value.FootprintReference2),
(erpPart.Value.FootprintReference3)
}.Where(d => !string.IsNullOrWhiteSpace(d));
List<string> foundFootprints = new List<string>();
foreach (var footprint in footprints)
{
if (fpLibLocations.TryGetValue(footprint, out var location))
{
foundFootprints.Add(location);
}
else
{
Console.WriteLine($"{part.Ident}: FP {footprint} nicht gefunden");
}
}
part.Footprints = string.Join(",", foundFootprints);
part.Manufacturer1 = erpPart.Value.Manufacturer1;
part.Manufacturer2 = erpPart.Value.Manufacturer2;
part.Manufacturer3 = erpPart.Value.Manufacturer3;
part.Manufacturer4 = erpPart.Value.Manufacturer4;
part.Manufacturer5 = erpPart.Value.Manufacturer5;
part.ManufacturerPartNumber1 = erpPart.Value.ManufacturerPartNumber1;
part.ManufacturerPartNumber2 = erpPart.Value.ManufacturerPartNumber2;
part.ManufacturerPartNumber3 = erpPart.Value.ManufacturerPartNumber3;
part.ManufacturerPartNumber4 = erpPart.Value.ManufacturerPartNumber4;
part.ManufacturerPartNumber5 = erpPart.Value.ManufacturerPartNumber5;
part.Package = erpPart.Value.Package;
part.PackageGroup = erpPart.Value.PackageGroup;
parts.Add(part);
}
using var kicadLibrary = new KicadLibraryContext();
kicadLibrary.Parts.RemoveRange(kicadLibrary.Parts);
kicadLibrary.Parts.AddRange(parts);
kicadLibrary.SaveChanges();
The whole thing is currently a proof on concept. We’re not using it in production right now but I could successfully place parts in Kicad and the footprints were tied in as well. It sure needs some fine tweaking if it’s going into production but I’d like to see some changes in Kicad before we’re actually making the switch:
I think I wrote about these in another post but in general I’d like to use the one part / file strategy and not have to add each symlib library file to the Kicad library manager. Just add the kicad_dbl file and you should be done.
I hope that helps; I know it’s a bit unstructured but it’s a proof-of-concept which worked quite fine. Certainly you’ll need to tie it to your requirements. The SExpression code and CParser code were both taken from GitHub - bobc/eakit: A tool to convert EAGLE(tm) CAD projects to KiCad.