skill
Fp3dView — Footprint Pad 3D Viewer
Public Made by Adomby adom
Interactive 3D viewer that displays KiCad .kicad_mod footprint pad geometry as extruded copper pads with outlines, labels, measurements, and a datasheet-style land pattern overlay in the Gallia Viewer
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
<!DOCTYPE html>
<html>
<head><script>(function(){var n=0;function show(p,m){var el=document.createElement('div');el.style.cssText='position:fixed;left:0;right:0;background:#e04040;color:#fff;padding:6px 12px;z-index:'+(99999-n)+';font:13px/1.4 monospace;white-space:pre-wrap;border-bottom:1px solid #b02020;top:'+n*30+'px';el.textContent=p+m;document.body.appendChild(el);n++}window.onerror=function(msg,src,line){show('ERROR '+(src||'').split('/').pop()+':'+line+' \u2014 ',msg)};window.addEventListener('unhandledrejection',function(e){show('REJECT \u2014 ',e.reason&&e.reason.message||e.reason||'unknown')})})()</script>
<meta charset="utf-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #0d1117; color: #e6edf3; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif; overflow: hidden; height: 100vh; }
.scene { position: relative; width: 100%; height: 100%; }
canvas { display: block; width: 100%; height: 100%; }
.toolbar { position: absolute; top: 10px; left: 50%; transform: translateX(-50%); display: flex; gap: 1px; background: #1a1a1a; border: 1px solid #333; border-radius: 6px; padding: 2px; z-index: 10; }
.toolbar button { width: 32px; height: 28px; background: transparent; border: none; border-radius: 4px; cursor: pointer; display: flex; align-items: center; justify-content: center; }
.toolbar button svg { width: 18px; height: 18px; stroke: #666; stroke-width: 1.5; fill: none; }
.toolbar button.active svg { stroke: #00b8b0; }
.badge { position: absolute; top: 10px; right: 10px; background: rgba(0,184,176,0.15); color: #00b8b0; font-size: 11px; font-weight: 600; padding: 3px 8px; border-radius: 4px; border: 1px solid rgba(0,184,176,0.3); z-index: 10; }
.footer { position: absolute; bottom: 8px; left: 50%; transform: translateX(-50%); color: #484f58; font-size: 11px; z-index: 10; }
</style>
</head>
<body>
<div class="scene">
<canvas id="c"></canvas>
<div class="toolbar">
<button title="Zoom to fit"><svg viewBox="0 0 24 24"><path d="M4 8V4h4M20 8V4h-4M4 16v4h4M20 16v4h-4"/><circle cx="12" cy="12" r="3"/></svg></button>
<button class="active" title="Outlines"><svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/></svg></button>
<button class="active" title="Pad numbers"><svg viewBox="0 0 24 24"><text x="12" y="16" text-anchor="middle" font-size="14" fill="none" stroke="#00b8b0" stroke-width="1.2">#</text></svg></button>
<button title="Pad names"><svg viewBox="0 0 24 24"><text x="12" y="16" text-anchor="middle" font-size="14" fill="none" stroke="#666" stroke-width="1.2">N</text></svg></button>
<button title="2D mesh"><svg viewBox="0 0 24 24"><path d="M3 3l18 18M3 12l18 0M12 3l0 18"/></svg></button>
<button title="Copper"><svg viewBox="0 0 24 24"><path d="M4 6h16v12H4z"/><path d="M4 6l3-3h10l3 3"/></svg></button>
<button title="Ground"><svg viewBox="0 0 24 24"><line x1="6" y1="18" x2="18" y2="18"/><line x1="9" y1="21" x2="15" y2="21"/><line x1="12" y1="6" x2="12" y2="18"/></svg></button>
<button title="Axes"><svg viewBox="0 0 24 24"><line x1="12" y1="12" x2="20" y2="12" stroke="#e74040"/><line x1="12" y1="12" x2="12" y2="4" stroke="#40a0e7"/><line x1="12" y1="12" x2="6" y2="18" stroke="#40e740"/></svg></button>
<button title="Dimensions"><svg viewBox="0 0 24 24"><line x1="3" y1="20" x2="21" y2="20"/><line x1="3" y1="18" x2="3" y2="22"/><line x1="21" y1="18" x2="21" y2="22"/><text x="12" y="12" text-anchor="middle" font-size="9" fill="none" stroke="#666" stroke-width="1.2">mm</text></svg></button>
<button class="active" title="Land pattern"><svg viewBox="0 0 24 24"><rect x="6" y="4" width="12" height="16" rx="1" fill="none"/><rect x="2" y="6" width="4" height="2.5" rx="0.3" fill="none"/><rect x="2" y="10.5" width="4" height="2.5" rx="0.3" fill="none"/><rect x="2" y="15" width="4" height="2.5" rx="0.3" fill="none"/><rect x="18" y="6" width="4" height="2.5" rx="0.3" fill="none"/><rect x="18" y="10.5" width="4" height="2.5" rx="0.3" fill="none"/><rect x="18" y="15" width="4" height="2.5" rx="0.3" fill="none"/></svg></button>
</div>
<div class="badge">Fp3dView</div>
<div class="footer">Demo — land pattern overlay (simulated SOIC-8)</div>
</div>
<script>
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
var W, H;
function resize() { W = canvas.width = canvas.offsetWidth; H = canvas.height = canvas.offsetHeight; draw(); }
window.addEventListener('resize', resize);
var pads = [
{n:'1',x:-2.475,y:-1.905,w:1.95,h:0.6},
{n:'2',x:-2.475,y:-0.635,w:1.95,h:0.6},
{n:'3',x:-2.475,y:0.635,w:1.95,h:0.6},
{n:'4',x:-2.475,y:1.905,w:1.95,h:0.6},
{n:'5',x:2.475,y:1.905,w:1.95,h:0.6},
{n:'6',x:2.475,y:0.635,w:1.95,h:0.6},
{n:'7',x:2.475,y:-0.635,w:1.95,h:0.6},
{n:'8',x:2.475,y:-1.905,w:1.95,h:0.6},
{n:'9',x:0,y:0,w:2.29,h:3}
];
// F.Fab body outline (chamfered pin-1 corner)
var fabPoly = [[-0.975,-2.45],[1.95,-2.45],[1.95,2.45],[-1.95,2.45],[-1.95,-1.475]];
function draw() {
ctx.fillStyle = '#1a1f26';
ctx.fillRect(0, 0, W, H);
var scale = Math.min(W, H) / 16;
var cx = W * 0.46, cy = H * 0.44;
var py_scale = 0.75;
// Green F.Fab body outline
ctx.strokeStyle = '#33cc44';
ctx.lineWidth = 2;
ctx.beginPath();
fabPoly.forEach(function(pt, i) {
var px = cx + pt[0] * scale;
var py = cy + pt[1] * scale * py_scale;
if (i === 0) ctx.moveTo(px, py); else ctx.lineTo(px, py);
});
ctx.closePath();
ctx.stroke();
// Blue pad outlines
pads.forEach(function(p) {
var px = cx + p.x * scale;
var py = cy + p.y * scale * py_scale;
var pw = p.w * scale;
var ph = p.h * scale * py_scale;
var r = Math.min(pw, ph) * 0.25;
ctx.strokeStyle = '#3a8fd4';
ctx.lineWidth = 1.5;
roundRect(ctx, px - pw/2, py - ph/2, pw, ph, r);
ctx.stroke();
// Pad number
ctx.fillStyle = '#ffffff';
ctx.font = (scale * 0.35) + 'px monospace';
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.fillText(p.n, px + pw/2 + scale * 0.12, py);
});
// Dimension annotations
ctx.strokeStyle = '#aaaaaa';
ctx.fillStyle = '#cccccc';
ctx.lineWidth = 1;
ctx.font = (scale * 0.32) + 'px monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// Overall width: 6.90
var ow_y = cy - 2.5 * scale * py_scale - scale * 0.8;
dimLineH(cx - 3.45 * scale, cx + 3.45 * scale, ow_y, '6.90');
// Pad width: 1.95
var pw_y = cy - 1.905 * scale * py_scale - scale * 0.55;
var pw_left = cx + (-2.475 - 0.975) * scale;
var pw_right = cx + (-2.475 + 0.975) * scale;
dimLineH(pw_left, pw_right, pw_y, '1.95');
// Pad height: 0.60
var ph_x = cx + (-2.475 - 0.975) * scale - scale * 0.6;
var ph_top = cy + (-1.905 - 0.3) * scale * py_scale;
var ph_bot = cy + (-1.905 + 0.3) * scale * py_scale;
dimLineV(ph_x, ph_top, ph_bot, '0.60');
// Pitch: 1.27
var pitch_x = cx + (-2.475 - 0.975) * scale - scale * 1.3;
var pitch_top = cy + (-1.905) * scale * py_scale;
var pitch_bot = cy + (-0.635) * scale * py_scale;
dimLineV(pitch_x, pitch_top, pitch_bot, '1.27');
// Pad center-to-center: 4.95
var cc_y = cy + 2.5 * scale * py_scale + scale * 0.8;
dimLineH(cx - 2.475 * scale, cx + 2.475 * scale, cc_y, '4.95');
// Overall height: 4.81
var oh_x = cx + 3.45 * scale + scale * 0.8;
var oh_top = cy - 2.205 * scale * py_scale;
var oh_bot = cy + 2.205 * scale * py_scale;
dimLineV(oh_x, oh_top, oh_bot, '4.81');
}
function dimLineH(x1, x2, y, label) {
var tk = 4;
ctx.beginPath(); ctx.moveTo(x1, y); ctx.lineTo(x2, y); ctx.stroke();
ctx.beginPath(); ctx.moveTo(x1, y - tk); ctx.lineTo(x1, y + tk); ctx.stroke();
ctx.beginPath(); ctx.moveTo(x2, y - tk); ctx.lineTo(x2, y + tk); ctx.stroke();
ctx.fillText(label, (x1 + x2) / 2, y - 10);
}
function dimLineV(x, y1, y2, label) {
var tk = 4;
ctx.beginPath(); ctx.moveTo(x, y1); ctx.lineTo(x, y2); ctx.stroke();
ctx.beginPath(); ctx.moveTo(x - tk, y1); ctx.lineTo(x + tk, y1); ctx.stroke();
ctx.beginPath(); ctx.moveTo(x - tk, y2); ctx.lineTo(x + tk, y2); ctx.stroke();
ctx.save();
ctx.translate(x - 10, (y1 + y2) / 2);
ctx.rotate(-Math.PI / 2);
ctx.fillText(label, 0, 0);
ctx.restore();
}
function roundRect(ctx, x, y, w, h, r) {
ctx.beginPath();
ctx.moveTo(x + r, y); ctx.lineTo(x + w - r, y);
ctx.quadraticCurveTo(x + w, y, x + w, y + r);
ctx.lineTo(x + w, y + h - r);
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
ctx.lineTo(x + r, y + h);
ctx.quadraticCurveTo(x, y + h, x, y + h - r);
ctx.lineTo(x, y + r);
ctx.quadraticCurveTo(x, y, x + r, y);
ctx.closePath();
}
resize();
</script>
</body>
</html>