skill
Make a Wiki Component Page
Public Made by Adomby adom
Create a wiki component page the RIGHT way. Pages are keyed manufacturer-mpn and scoped to ONE DATASHEET, so a datasheet covering six voltages and three packages is ONE page covering all of them, never six pages. Two manufacturers publishing the same MPN are two pages, because their limits genuinely differ. Renders through a hand-built readme.html on the Adom theme tokens with a fixed ten-section shape (identity, at a glance, specifications, symbol+footprint viewers, pin map, variants, assets, i
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
#!/usr/bin/env python3
"""
adom-footprint 1.0.20 emits empty <g class="adom-layer-{fab,silk,courtyard}"> groups —
it only ever populates copper/drill/padnum. The viewer's own LAYER_DEFS already define
those three layers and `groupHasGeo()` shows a toggle row as soon as a group holds
geometry, so filling the groups is enough to light up the layers panel. No JS changes.
Parses the graphics items out of a .kicad_mod and injects matching SVG into the groups,
widening the viewBox when the courtyard/fab outline sits outside the copper extents.
"""
import re, sys, math
COLORS = {"fab": "#6e7681", "silk": "#00cccc", "courtyard": "#FF26E2"}
LAYER_OF = {"F.Fab": "fab", "F.SilkS": "silk", "F.CrtYd": "courtyard"}
def sexp(text):
"""Minimal s-expression reader -> nested lists of tokens."""
toks = re.findall(r'\(|\)|"(?:[^"\\]|\\.)*"|[^\s()]+', text)
stack, cur = [], []
for t in toks:
if t == "(":
stack.append(cur); cur = []
elif t == ")":
done, cur = cur, stack.pop(); cur.append(done)
else:
cur.append(t[1:-1] if t.startswith('"') else t)
return cur
def find(node, key):
return [c for c in node if isinstance(c, list) and c and c[0] == key]
def one(node, key):
r = find(node, key)
return r[0] if r else None
def xy(node, key):
n = one(node, key)
return (float(n[1]), float(n[2])) if n else None
def layer_of(item):
n = one(item, "layer")
return n[1] if n else None
def width_of(item):
st = one(item, "stroke")
if st:
w = one(st, "width")
if w:
return float(w[1])
w = one(item, "width")
return float(w[1]) if w else 0.12
def filled(item):
f = one(item, "fill")
if f and len(f) > 1:
v = f[1] if not isinstance(f[1], list) else (f[1][1] if len(f[1]) > 1 else "no")
return v in ("yes", "solid", "true")
return False
def arc_path(s, m, e):
"""Three-point arc -> SVG A command."""
(x1, y1), (xm, ym), (x2, y2) = s, m, e
d = 2 * (x1 * (ym - y2) + xm * (y2 - y1) + x2 * (y1 - ym))
if abs(d) < 1e-12:
return f"M {x1:.4f} {y1:.4f} L {x2:.4f} {y2:.4f}"
ux = ((x1**2 + y1**2) * (ym - y2) + (xm**2 + ym**2) * (y2 - y1) + (x2**2 + y2**2) * (y1 - ym)) / d
uy = ((x1**2 + y1**2) * (x2 - xm) + (xm**2 + ym**2) * (x1 - x2) + (x2**2 + y2**2) * (xm - x1)) / d
r = math.hypot(x1 - ux, y1 - uy)
cross = (xm - x1) * (y2 - y1) - (ym - y1) * (x2 - x1)
sweep = 0 if cross > 0 else 1
a1 = math.atan2(y1 - uy, x1 - ux); a2 = math.atan2(y2 - uy, x2 - ux)
da = (a2 - a1) % (2 * math.pi) if sweep else (a1 - a2) % (2 * math.pi)
large = 1 if da > math.pi else 0
return f"M {x1:.4f} {y1:.4f} A {r:.4f} {r:.4f} 0 {large} {sweep} {x2:.4f} {y2:.4f}"
def collect(fp):
"""-> {layer_key: [svg_element, ...]}, plus the bbox of everything drawn."""
out = {k: [] for k in COLORS}
xs, ys = [], []
def note(*pts):
for x, y in pts:
xs.append(x); ys.append(y)
for item in fp:
if not isinstance(item, list) or not item:
continue
kind = item[0]
if not kind.startswith("fp_"):
continue
key = LAYER_OF.get(layer_of(item) or "")
if not key:
continue
col, w = COLORS[key], width_of(item)
common = f'stroke="{col}" stroke-width="{w:.3f}" stroke-linecap="round" fill="none"'
if kind == "fp_line":
s, e = xy(item, "start"), xy(item, "end")
if s and e:
note(s, e)
out[key].append(f'<line x1="{s[0]:.4f}" y1="{s[1]:.4f}" x2="{e[0]:.4f}" y2="{e[1]:.4f}" {common}/>')
elif kind == "fp_rect":
s, e = xy(item, "start"), xy(item, "end")
if s and e:
note(s, e)
x, y = min(s[0], e[0]), min(s[1], e[1])
fillv = col if filled(item) else "none"
out[key].append(f'<rect x="{x:.4f}" y="{y:.4f}" width="{abs(e[0]-s[0]):.4f}" '
f'height="{abs(e[1]-s[1]):.4f}" stroke="{col}" stroke-width="{w:.3f}" fill="{fillv}"/>')
elif kind == "fp_circle":
c, e = xy(item, "center"), xy(item, "end")
if c and e:
r = math.hypot(e[0] - c[0], e[1] - c[1])
note((c[0] - r, c[1] - r), (c[0] + r, c[1] + r))
fillv = col if filled(item) else "none"
out[key].append(f'<circle cx="{c[0]:.4f}" cy="{c[1]:.4f}" r="{r:.4f}" '
f'stroke="{col}" stroke-width="{w:.3f}" fill="{fillv}"/>')
elif kind == "fp_arc":
s, m, e = xy(item, "start"), xy(item, "mid"), xy(item, "end")
if s and m and e:
note(s, m, e)
out[key].append(f'<path d="{arc_path(s, m, e)}" {common}/>')
elif kind == "fp_poly":
pts = one(item, "pts")
if pts:
p = [(float(c[1]), float(c[2])) for c in find(pts, "xy")]
if p:
note(*p)
fillv = col if filled(item) else "none"
d = " ".join(f"{x:.4f},{y:.4f}" for x, y in p)
out[key].append(f'<polygon points="{d}" stroke="{col}" stroke-width="{w:.3f}" fill="{fillv}"/>')
bbox = (min(xs), min(ys), max(xs), max(ys)) if xs else None
return out, bbox
def inject(html_path, mod_path, out_path):
fp = sexp(open(mod_path).read())[0]
layers, bbox = collect(fp)
html = open(html_path).read()
for key, els in layers.items():
if not els:
continue
tag = f'<g class="adom-layer-{key}"></g>'
if tag not in html:
print(f" ! no empty {key} group to fill", file=sys.stderr); continue
html = html.replace(tag, f'<g class="adom-layer-{key}">{"".join(els)}</g>', 1)
# widen the viewBox if the outline geometry falls outside the copper extents
m = re.search(r'viewBox="(-?[\d.]+) (-?[\d.]+) ([\d.]+) ([\d.]+)"', html)
if m and bbox:
vx, vy, vw, vh = (float(g) for g in m.groups())
nx0, ny0 = min(vx, bbox[0]), min(vy, bbox[1])
nx1, ny1 = max(vx + vw, bbox[2]), max(vy + vh, bbox[3])
pad = 0.15
nx0 -= pad; ny0 -= pad; nx1 += pad; ny1 += pad
if (nx0, ny0, nx1 - nx0, ny1 - ny0) != (vx, vy, vw, vh):
html = html.replace(m.group(0),
f'viewBox="{nx0:.3f} {ny0:.3f} {nx1-nx0:.3f} {ny1-ny0:.3f}"', 1)
open(out_path, "w").write(html)
print(f" {out_path}: " + ", ".join(f"{k}={len(v)}" for k, v in layers.items() if v))
if __name__ == "__main__":
inject(*sys.argv[1:4])