app
Adom Symbol
Public Made by Adomby adom
KiCad symbol creator with interactive viewer and delivery to KiCad/Fusion 360
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
/**
* EAGLE .lbr Generator
*
* Converts KiCad .kicad_sym symbol data to EAGLE XML format (.lbr)
* for Fusion 360 Electronics delivery.
*
* Coordinate mapping: Both use mm with Y-up, so it's a direct mapping.
*/
// KiCad pin type → EAGLE direction
const PIN_TYPE_MAP = {
power_in: 'pwr',
power_out: 'out',
input: 'in',
output: 'out',
bidirectional: 'io',
passive: 'pas',
unconnected: 'nc',
tri_state: 'hiz',
unspecified: 'io',
open_collector: 'oc',
open_emitter: 'oc',
free: 'io',
};
// KiCad angle → EAGLE rotation
const ANGLE_MAP = {
0: '', // extends right (left-side pin)
180: ' rot="R180"', // extends left (right-side pin)
90: ' rot="R90"', // extends up (bottom-side pin)
270: ' rot="R270"', // extends down (top-side pin)
};
// KiCad pin length → EAGLE length keyword
function pinLengthKeyword(len) {
if (len <= 0) return 'point';
if (len <= 2.54) return 'short';
if (len <= 5.08) return 'middle';
return 'long';
}
// Escape XML special characters
function escXml(s) {
if (!s) return '';
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
}
// Standard EAGLE layer definitions (required in every .lbr)
const LAYERS_XML = `<layers>
<layer number="1" name="Top" color="4" fill="1" visible="yes" active="yes"/>
<layer number="16" name="Bottom" color="1" fill="1" visible="yes" active="yes"/>
<layer number="17" name="Pads" color="2" fill="1" visible="yes" active="yes"/>
<layer number="18" name="Vias" color="2" fill="1" visible="yes" active="yes"/>
<layer number="19" name="Unrouted" color="6" fill="1" visible="yes" active="yes"/>
<layer number="20" name="Dimension" color="15" fill="1" visible="yes" active="yes"/>
<layer number="21" name="tPlace" color="7" fill="1" visible="yes" active="yes"/>
<layer number="22" name="bPlace" color="7" fill="1" visible="yes" active="yes"/>
<layer number="23" name="tOrigins" color="15" fill="1" visible="yes" active="yes"/>
<layer number="24" name="bOrigins" color="15" fill="1" visible="yes" active="yes"/>
<layer number="25" name="tNames" color="7" fill="1" visible="yes" active="yes"/>
<layer number="26" name="bNames" color="7" fill="1" visible="yes" active="yes"/>
<layer number="27" name="tValues" color="7" fill="1" visible="yes" active="yes"/>
<layer number="28" name="bValues" color="7" fill="1" visible="yes" active="yes"/>
<layer number="29" name="tStop" color="7" fill="3" visible="no" active="yes"/>
<layer number="30" name="bStop" color="7" fill="6" visible="no" active="yes"/>
<layer number="31" name="tCream" color="7" fill="4" visible="no" active="yes"/>
<layer number="32" name="bCream" color="7" fill="5" visible="no" active="yes"/>
<layer number="33" name="tFinish" color="6" fill="3" visible="no" active="yes"/>
<layer number="34" name="bFinish" color="6" fill="6" visible="no" active="yes"/>
<layer number="35" name="tGlue" color="7" fill="4" visible="no" active="yes"/>
<layer number="36" name="bGlue" color="7" fill="5" visible="no" active="yes"/>
<layer number="37" name="tTest" color="7" fill="1" visible="no" active="yes"/>
<layer number="38" name="bTest" color="7" fill="1" visible="no" active="yes"/>
<layer number="39" name="tKeepout" color="4" fill="11" visible="no" active="yes"/>
<layer number="40" name="bKeepout" color="1" fill="11" visible="no" active="yes"/>
<layer number="41" name="tRestrict" color="4" fill="10" visible="no" active="yes"/>
<layer number="42" name="bRestrict" color="1" fill="10" visible="no" active="yes"/>
<layer number="43" name="vRestrict" color="2" fill="10" visible="no" active="yes"/>
<layer number="44" name="Drills" color="7" fill="1" visible="no" active="yes"/>
<layer number="45" name="Holes" color="7" fill="1" visible="no" active="yes"/>
<layer number="46" name="Milling" color="3" fill="1" visible="no" active="yes"/>
<layer number="47" name="Measures" color="7" fill="1" visible="no" active="yes"/>
<layer number="48" name="Document" color="7" fill="1" visible="yes" active="yes"/>
<layer number="49" name="Reference" color="7" fill="1" visible="yes" active="yes"/>
<layer number="91" name="Nets" color="2" fill="1" visible="yes" active="yes"/>
<layer number="92" name="Busses" color="1" fill="1" visible="yes" active="yes"/>
<layer number="93" name="Pins" color="2" fill="1" visible="no" active="yes"/>
<layer number="94" name="Symbols" color="4" fill="1" visible="yes" active="yes"/>
<layer number="95" name="Names" color="7" fill="1" visible="yes" active="yes"/>
<layer number="96" name="Values" color="7" fill="1" visible="yes" active="yes"/>
<layer number="97" name="Info" color="7" fill="1" visible="yes" active="yes"/>
<layer number="98" name="Guide" color="6" fill="1" visible="yes" active="yes"/>
</layers>`;
/**
* Generate EAGLE .lbr XML from parsed symbol data.
*
* @param {object} input
* @param {string} input.symbolName
* @param {string} input.description
* @param {string} [input.referencePrefix='U']
* @param {Array<{name: string, number: string, type: string, x: number, y: number, angle: number, length: number}>} input.pins
* @param {object} input.body - Body rectangle { left, right, top, bottom }
* @param {Array<{name: string, x: number, y: number, justify: string}>} [input.groupLabels]
* @returns {string} Complete EAGLE .lbr XML content
*/
export function generateLbr(input) {
const {
symbolName,
description = '',
referencePrefix = 'U',
pins,
body,
groupLabels = [],
} = input;
const lines = [];
lines.push('<?xml version="1.0" encoding="utf-8"?>');
lines.push('<!DOCTYPE eagle SYSTEM "eagle.dtd">');
lines.push('<eagle version="9.6.2">');
lines.push('<drawing>');
lines.push('<settings><setting alwaysvectorfont="no"/></settings>');
lines.push('<grid distance="0.1" unitdist="inch" unit="inch" style="lines" multiple="1" display="no" altdistance="0.01" altunitdist="inch" altunit="inch"/>');
lines.push(LAYERS_XML);
lines.push('<library>');
lines.push(`<description>${escXml(symbolName)} — ${escXml(description)}</description>`);
lines.push('<packages></packages>');
lines.push('<symbols>');
lines.push(`<symbol name="${escXml(symbolName)}">`);
// Body rectangle (4 wires on layer 94)
if (body) {
lines.push(` <wire x1="${body.left}" y1="${body.top}" x2="${body.right}" y2="${body.top}" width="0.254" layer="94"/>`);
lines.push(` <wire x1="${body.right}" y1="${body.top}" x2="${body.right}" y2="${body.bottom}" width="0.254" layer="94"/>`);
lines.push(` <wire x1="${body.right}" y1="${body.bottom}" x2="${body.left}" y2="${body.bottom}" width="0.254" layer="94"/>`);
lines.push(` <wire x1="${body.left}" y1="${body.bottom}" x2="${body.left}" y2="${body.top}" width="0.254" layer="94"/>`);
}
// >NAME and >VALUE labels
const nameY = body ? body.top + 2.54 : 10;
const valueY = body ? body.bottom - 2.54 : -10;
lines.push(` <text x="0" y="${nameY}" size="1.27" layer="95" align="bottom-center">>NAME</text>`);
lines.push(` <text x="0" y="${valueY}" size="1.27" layer="96" align="top-center">>VALUE</text>`);
// Group labels on layer 94
for (const label of groupLabels) {
const align = label.justify === 'right' ? ' align="bottom-right"' : '';
lines.push(` <text x="${label.x}" y="${label.y}" size="0.762" layer="94"${align}>${escXml(label.name)}</text>`);
}
// Pins
// Track NC pin names for deduplication
const ncCounts = {};
for (const pin of pins) {
let pinName = pin.name;
// EAGLE requires unique pin names — use @suffix for NC pins
if (/^NC$/i.test(pinName)) {
ncCounts.NC = (ncCounts.NC || 0) + 1;
if (ncCounts.NC > 1) pinName = `NC@${ncCounts.NC}`;
}
const dir = PIN_TYPE_MAP[pin.type] || 'io';
const rot = ANGLE_MAP[pin.angle] || '';
const len = pinLengthKeyword(pin.length || 2.54);
lines.push(` <pin name="${escXml(pinName)}" x="${pin.x}" y="${pin.y}" length="${len}" direction="${dir}"${rot}/>`);
}
lines.push('</symbol>');
lines.push('</symbols>');
// Deviceset
lines.push('<devicesets>');
lines.push(`<deviceset name="${escXml(symbolName)}" prefix="${escXml(referencePrefix)}">`);
lines.push(` <description>${escXml(description)}</description>`);
lines.push(' <gates>');
lines.push(` <gate name="G$1" symbol="${escXml(symbolName)}" x="0" y="0"/>`);
lines.push(' </gates>');
lines.push(' <devices>');
lines.push(' <device name="">');
lines.push(' <connects/>');
lines.push(' <technologies><technology name=""/></technologies>');
lines.push(' </device>');
lines.push(' </devices>');
lines.push('</deviceset>');
lines.push('</devicesets>');
lines.push('</library>');
lines.push('</drawing>');
lines.push('</eagle>');
return lines.join('\n');
}